ניתוח נתונים חקרני (EDA) בפייתון: מדריך מלא עם pandas 3.0, matplotlib ו-seaborn

מדריך מעשי לניתוח נתונים חקרני (EDA) בפייתון עם pandas 3.0, matplotlib ו-seaborn. כולל דוגמאות קוד להרצה ישירה, ויזואליזציות ושלבים ברורים מטעינת הנתונים ועד סיכום הממצאים.

מהו ניתוח נתונים חקרני (EDA) ולמה הוא חיוני?

אם אתם עובדים עם נתונים — בין אם זה פרויקט לימודי, עבודה, או סתם סקרנות — ניתוח נתונים חקרני (Exploratory Data Analysis, בקיצור EDA) הוא השלב שאסור לדלג עליו. זה השלב שבו אתם באמת מכירים את הנתונים שלכם לפני שרצים לבנות מודלים.

מה הכוונה "להכיר את הנתונים"? בעצם, לענות על שאלות בסיסיות: מה המבנה? איפה חסרים ערכים? אילו דפוסים מסתתרים שם? ואילו קשרים מפתיעים קיימים בין המשתנים?

הרעיון הזה לא חדש. ג׳ון טוקי (John Tukey) הציג אותו כבר בשנות ה-70, אבל הוא רלוונטי היום יותר מאי פעם. מדעני נתונים מקדישים כ-80% מזמנם לניקוי וחקירת נתונים — ו-EDA הוא הכלי המרכזי לכך.

אז בואו נצלול פנימה. במדריך הזה נעבור צעד אחר צעד על תהליך EDA מלא, עם pandas 3.0 (שיצאה בינואר 2026), matplotlib ו-seaborn. כל קטע קוד ניתן להרצה ישירה ב-Jupyter Notebook.

הכנת סביבת העבודה

התקנת הספריות הנדרשות

לפני הכל, נוודא שהספריות מותקנות. פתחו טרמינל או תא קוד ב-Jupyter והריצו:

pip install pandas>=3.0 matplotlib seaborn numpy

טיפ חשוב: מאוד מומלץ להתקין גם את pyarrow. למה? כי pandas 3.0 משתמשת בו מאחורי הקלעים לטיפוס המחרוזות החדש (str dtype) ולשיפורי ביצועים משמעותיים:

pip install pyarrow

ייבוא הספריות

כל מחברת Jupyter טובה מתחילה עם בלוק הייבוא. זה השלד הבסיסי שאני משתמש בו כמעט בכל פרויקט:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# הגדרות תצוגה
sns.set_theme(style="whitegrid")
plt.rcParams["figure.figsize"] = (10, 6)
pd.options.display.max_columns = 30

print(f"pandas version: {pd.__version__}")
print(f"seaborn version: {sns.__version__}")

שלב 1: טעינת הנתונים לתוך DataFrame

pandas תומכת במגוון רחב של פורמטים — CSV, Excel, JSON, SQL, Parquet ועוד. בדוגמה שלנו נעבוד עם מערך נתוני הדיור בקליפורניה, שמתאים מצוין ללימוד EDA:

# טעינת נתונים מקובץ CSV
df = pd.read_csv("housing_data.csv")

# לחלופין, יצירת דאטהסט לדוגמה
from sklearn.datasets import fetch_california_housing
data = fetch_california_housing(as_frame=True)
df = data.frame
df.head()

שימו לב לשינוי ב-pandas 3.0: עמודות טקסט מזוהות עכשיו אוטומטית כטיפוס str החדש (במקום object הישן). זה לא סתם שינוי קוסמטי — הטיפוס החדש תומך רק במחרוזות וערכים חסרים, ומספק ביצועים טובים יותר בזכות PyArrow.

שלב 2: סקירה ראשונית של הנתונים

מבנה הנתונים

הפקודה הראשונה שאני מריץ תמיד — בלי יוצא מהכלל — היא df.info(). היא נותנת את כל המידע הקריטי בשורה אחת:

df.info()

מה נקבל מהפלט?

  • מספר השורות והעמודות
  • שם כל עמודה וטיפוס הנתונים שלה
  • כמה ערכים לא חסרים יש (Non-Null Count)
  • צריכת הזיכרון

סטטיסטיקה תיאורית

הפקודה describe() מחשבת סטטיסטיקות בסיסיות לכל עמודה מספרית. אני אוהב להוסיף טרנספוזיציה:

df.describe().T

ה-.T הופך את הטבלה כך שכל עמודה מקורית מוצגת כשורה — נוח הרבה יותר לקריאה כשיש הרבה עמודות.

דבר ששווה לשים לב אליו: הפער בין mean ל-50% (החציון). פער גדול מרמז על התפלגות לא סימטרית, וזה משהו שנצטרך לטפל בו בהמשך.

בדיקת הצצה ראשונית

# חמש השורות הראשונות
df.head()

# חמש השורות האחרונות
df.tail()

# שורות אקראיות
df.sample(5)

# ממדי הנתונים
print(f"rows: {df.shape[0]}, columns: {df.shape[1]}")

שלב 3: זיהוי וטיפול בערכים חסרים

בואו נהיה כנים — ערכים חסרים הם כאב ראש. אבל הם חלק בלתי נפרד מהעבודה עם נתונים אמיתיים, וחייבים לזהות אותם מוקדם.

# סה"כ ערכים חסרים לכל עמודה
missing = df.isnull().sum()
missing_pct = (missing / len(df)) * 100

missing_report = pd.DataFrame({
    "missing_count": missing,
    "missing_pct": missing_pct
}).query("missing_count > 0").sort_values("missing_pct", ascending=False)

print(missing_report)

ויזואליזציה של ערכים חסרים

מספרים זה טוב, אבל גרף נותן תמונה ברורה יותר:

fig, ax = plt.subplots(figsize=(12, 5))
colors = ["#2ecc71" if x == 0 else "#e74c3c" for x in df.isnull().sum()]
df.isnull().sum().plot(kind="bar", ax=ax, color=colors)
ax.set_title("Missing Values per Column")
ax.set_ylabel("Count")
plt.xticks(rotation=45, ha="right")
plt.tight_layout()
plt.show()

אסטרטגיות לטיפול בערכים חסרים

אין פתרון אחד שמתאים לכולם. הבחירה תלויה באחוז הערכים החסרים ובאופי הנתונים:

  • מחיקת שורות: כשאחוז הערכים החסרים זניח (פחות מ-5%)
  • מילוי בממוצע/חציון: לעמודות מספריות עם התפלגות סבירה
  • מילוי בערך השכיח (mode): לעמודות קטגוריאליות
  • אינטרפולציה: לנתוני סדרות זמן
# מחיקת שורות עם ערכים חסרים
df_clean = df.dropna()

# מילוי בחציון (עמיד יותר לחריגים)
df["column_name"] = df["column_name"].fillna(df["column_name"].median())

# מילוי בערך השכיח
df["cat_column"] = df["cat_column"].fillna(df["cat_column"].mode()[0])

מניסיון אישי, החציון (median) עדיף כמעט תמיד על הממוצע למילוי ערכים חסרים — הוא פחות רגיש לערכים חריגים.

שלב 4: זיהוי וטיפול בכפילויות

שלב קצר אבל קריטי. כפילויות יכולות לעוות לגמרי את התוצאות:

# בדיקת כפילויות
dup_count = df.duplicated().sum()
print(f"Number of duplicate rows: {dup_count}")

# הסרת כפילויות
df = df.drop_duplicates()
print(f"Shape after removing duplicates: {df.shape}")

שלב 5: ניתוח חד-משתני (Univariate Analysis)

פה מתחיל החלק המעניין באמת. ניתוח חד-משתני בודק כל משתנה בנפרד — מה ההתפלגות שלו, האם יש ערכים חריגים, ומה הטווח.

היסטוגרמות למשתנים מספריים

numeric_cols = df.select_dtypes(include=np.number).columns

fig, axes = plt.subplots(
    nrows=(len(numeric_cols) + 2) // 3,
    ncols=3,
    figsize=(15, 4 * ((len(numeric_cols) + 2) // 3))
)
axes = axes.flatten()

for i, col in enumerate(numeric_cols):
    sns.histplot(df[col], kde=True, ax=axes[i], color="steelblue")
    axes[i].set_title(f"Distribution of {col}")
    axes[i].set_xlabel("")

# הסתרת צירים ריקים
for j in range(i + 1, len(axes)):
    axes[j].set_visible(False)

plt.tight_layout()
plt.show()

תרשים קופסה (Box Plot) לזיהוי חריגים

fig, axes = plt.subplots(
    nrows=(len(numeric_cols) + 2) // 3,
    ncols=3,
    figsize=(15, 4 * ((len(numeric_cols) + 2) // 3))
)
axes = axes.flatten()

for i, col in enumerate(numeric_cols):
    sns.boxplot(y=df[col], ax=axes[i], color="lightcoral")
    axes[i].set_title(f"Box Plot: {col}")

for j in range(i + 1, len(axes)):
    axes[j].set_visible(False)

plt.tight_layout()
plt.show()

רואים נקודות מחוץ ל-"שפם" של הקופסה? אלה ערכים חריגים. אל תמהרו למחוק אותם — הם עשויים להיות שגיאות מדידה, אבל גם תצפיות נדירות שדווקא מכילות מידע חשוב.

ניתוח משתנים קטגוריאליים

cat_cols = df.select_dtypes(include=["object", "category", "str"]).columns

for col in cat_cols:
    print(f"\n--- {col} ---")
    print(df[col].value_counts())
    
    fig, ax = plt.subplots(figsize=(8, 4))
    df[col].value_counts().head(10).plot(kind="barh", ax=ax, color="teal")
    ax.set_title(f"Top 10 Values: {col}")
    plt.tight_layout()
    plt.show()

שלב 6: ניתוח דו-משתני (Bivariate Analysis)

עד עכשיו הסתכלנו על כל משתנה בפני עצמו. עכשיו מגיע הרגע לבדוק מה קורה כשמשלבים שניים ביחד — האם יש מתאם? האם שינוי באחד משפיע על השני?

מטריצת מתאם (Correlation Matrix)

corr_matrix = df.select_dtypes(include=np.number).corr()

fig, ax = plt.subplots(figsize=(12, 10))
mask = np.triu(np.ones_like(corr_matrix, dtype=bool))
sns.heatmap(
    corr_matrix,
    mask=mask,
    annot=True,
    fmt=".2f",
    cmap="RdBu_r",
    center=0,
    square=True,
    linewidths=0.5,
    ax=ax
)
ax.set_title("Correlation Heatmap")
plt.tight_layout()
plt.show()

ערכים קרובים ל-1 או -1 מצביעים על מתאם חזק. ערכים קרובים ל-0? אין מתאם ליניארי (אבל ייתכן שיש קשר לא-ליניארי — שווה לזכור).

תרשימי פיזור (Scatter Plots)

# בחירת הזוגות עם המתאם החזק ביותר
top_corr = (corr_matrix
    .abs()
    .unstack()
    .sort_values(ascending=False)
    .drop_duplicates()
)
# סינון מתאמים של משתנה עם עצמו
top_corr = top_corr[top_corr < 1.0].head(5)

for (col1, col2), value in top_corr.items():
    fig, ax = plt.subplots(figsize=(8, 5))
    sns.scatterplot(x=df[col1], y=df[col2], alpha=0.5, ax=ax)
    ax.set_title(f"{col1} vs {col2} (r={value:.2f})")
    plt.tight_layout()
    plt.show()

Box Plot לפי קטגוריות

# אם יש עמודה קטגוריאלית ועמודה מספרית
if len(cat_cols) > 0 and len(numeric_cols) > 0:
    fig, ax = plt.subplots(figsize=(10, 6))
    sns.boxplot(
        data=df,
        x=cat_cols[0],
        y=numeric_cols[0],
        ax=ax,
        palette="Set2"
    )
    ax.set_title(f"{numeric_cols[0]} by {cat_cols[0]}")
    plt.xticks(rotation=45)
    plt.tight_layout()
    plt.show()

שלב 7: ניתוח רב-משתני (Multivariate Analysis)

Pair Plot

הגרף הזה הוא אחד האהובים עליי — הוא מציג את כל הצירופים האפשריים של זוגות משתנים במכה אחת. רק תגבילו את מספר העמודות, אחרת זה הופך לבלתי קריא:

# בחירת תת-קבוצה של עמודות (עד 5-6 כדי שיהיה קריא)
selected = numeric_cols[:5]
sns.pairplot(df[selected], diag_kind="kde", plot_kws={"alpha": 0.4})
plt.suptitle("Pair Plot of Selected Features", y=1.02)
plt.show()

Heatmap מתקדם עם אגרגציה

# pivot + heatmap — שימושי כשיש שתי עמודות קטגוריאליות
# ועמודה מספרית לאגרגציה
if len(cat_cols) >= 2:
    pivot = df.pivot_table(
        values=numeric_cols[0],
        index=cat_cols[0],
        columns=cat_cols[1],
        aggfunc="mean"
    )
    fig, ax = plt.subplots(figsize=(10, 8))
    sns.heatmap(pivot, annot=True, fmt=".1f", cmap="YlOrRd", ax=ax)
    ax.set_title(f"Mean {numeric_cols[0]} by {cat_cols[0]} and {cat_cols[1]}")
    plt.tight_layout()
    plt.show()

שלב 8: ניתוח התפלגויות וצורתן

הבנת צורת ההתפלגות חשובה מאוד, במיוחד אם אתם מתכננים להשתמש במודלים סטטיסטיים שמניחים נורמליות:

from scipy import stats

for col in numeric_cols:
    skewness = df[col].skew()
    kurtosis = df[col].kurtosis()
    print(f"{col}: skewness={skewness:.2f}, kurtosis={kurtosis:.2f}")
    
    if abs(skewness) > 1:
        print(f"  → {col} has high skewness — consider log transform")
  • Skewness (אסימטריה): ערך קרוב ל-0 = התפלגות סימטרית. חיובי = זנב ימני ארוך. שלילי = זנב שמאלי ארוך.
  • Kurtosis (שיפוד): ערך גבוה אומר זנבות כבדים — כלומר, יותר ערכים קיצוניים ממה שהיינו מצפים בהתפלגות נורמלית.

טרנספורמציה לוגריתמית

כשיש אסימטריה חזקה, טרנספורמציה לוגריתמית יכולה לעזור. ככה זה נראה לפני ואחרי:

# דוגמה לתיקון אסימטריה חיובית חזקה
skewed_col = numeric_cols[0]  # החליפו בעמודה הרלוונטית

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

sns.histplot(df[skewed_col], kde=True, ax=axes[0], color="salmon")
axes[0].set_title(f"Original: {skewed_col}")

sns.histplot(np.log1p(df[skewed_col]), kde=True, ax=axes[1], color="mediumseagreen")
axes[1].set_title(f"Log-Transformed: {skewed_col}")

plt.tight_layout()
plt.show()

שלב 9: סיכום הממצאים ומה הלאה

בסוף תהליך ה-EDA, חשוב (ממש חשוב) לתעד מה מצאתם. אני יודע, זה לא החלק הכי מרגש, אבל העתיד-אתם יודו לכם:

summary = f"""
=== EDA Summary ===
Dataset shape: {df.shape}
Numeric columns: {len(numeric_cols)}
Categorical columns: {len(cat_cols)}
Missing values: {df.isnull().sum().sum()}
Duplicate rows: {df.duplicated().sum()}

Key findings:
- Top correlated pairs: (fill in manually)
- Skewed features: (fill in from skewness analysis)
- Columns with missing data: {list(missing_report.index) if len(missing_report) > 0 else "None"}
"""
print(summary)

אז מה עושים עם כל הממצאים האלה? הנה האפשרויות העיקריות:

  1. הנדסת פיצ׳רים (Feature Engineering) — יצירת משתנים חדשים שמבוססים על מה שגיליתם
  2. בחירת אלגוריתם למידת מכונה מתאים, על בסיס סוג הנתונים והתפלגויותיהם
  3. החלטה על אסטרטגיית ניקוי — מה למחוק, מה למלא, ואילו טרנספורמציות להפעיל
  4. זיהוי משתנים מיותרים שאפשר להסיר (multicollinearity — כשהמתאם ביניהם גבוה מדי)

טיפים מתקדמים ל-pandas 3.0

pandas 3.0 יצאה בינואר 2026, ויש כמה שינויים שכדאי להכיר כי הם משפיעים ישירות על איך עושים EDA:

  • טיפוס מחרוזות חדש (str): עמודות טקסט מזוהות אוטומטית כ-str ולא object. יעיל יותר ותומך רק במחרוזות — מה שמונע בלבולים.
  • Copy-on-Write פעיל כברירת מחדל: זה שינוי שסוף סוף הגיע. כל פעולת חיתוך מחזירה עותק, לא תצוגה. זה מונע את הבאג הקלאסי של שינוי לא מכוון בנתונים המקוריים (מי שנתקל ב-SettingWithCopyWarning — הבשורה הזאת בשבילכם).
  • רזולוציית זמן חדשה: נתוני datetime עוברים ל-microseconds במקום nanoseconds. בפועל, זה אומר טווח תאריכים רחב הרבה יותר.
# בדיקת הטיפוסים החדשים ב-pandas 3.0
df_example = pd.DataFrame({"name": ["Alice", "Bob"], "age": [30, 25]})
print(df_example.dtypes)
# name    str       ← חדש! (במקום object)
# age     int64

שאלות נפוצות

מה ההבדל בין EDA לבין ניקוי נתונים?

ניקוי נתונים הוא חלק מ-EDA, אבל EDA רחב הרבה יותר. ניקוי מתמקד בתיקון בעיות ספציפיות — ערכים חסרים, כפילויות, פורמטים שגויים. EDA כולל גם חקירה של התפלגויות, מתאמים ודפוסים. בפועל, השניים קורים במקביל וקשה להפריד ביניהם.

כמה זמן לוקח תהליך EDA?

תלוי. מערך נתונים קטן עם כמה אלפי שורות? אפשר לסיים EDA בסיסי תוך שעה. מערך נתונים גדול ומורכב עם עשרות עמודות? זה יכול לקחת ימים. הדבר החשוב הוא לא למהר — עדיף להשקיע זמן ב-EDA מאשר לגלות בעיות בשלב המודלינג.

אילו כלים אוטומטיים קיימים ל-EDA?

יש כמה ספריות שוות: ydata-profiling (לשעבר pandas-profiling) מייצרת דוח HTML מקיף בפקודה אחת, sweetviz מעולה להשוואת מערכי נתונים, ו-dataprep מציעה פונקציות EDA מהירות. אבל — ואני חייב להגיד את זה — כלים אוטומטיים הם נקודת התחלה, לא תחליף להבנה עמוקה של הנתונים.

חייבים seaborn או שמספיק matplotlib?

matplotlib לבדה מספיקה לכל סוגי הגרפים, אבל seaborn חוסכת המון זמן. לדוגמה, heatmap של מטריצת מתאם זה שורת קוד אחת ב-seaborn לעומת עשרות שורות ב-matplotlib טהורה. הגישה שלי: להשתמש ב-seaborn לגרפים סטטיסטיים, ולהוסיף matplotlib כשצריך התאמה אישית מדויקת.

מה עושים עם ערכים חריגים?

הכלל הראשון: לא למחוק אוטומטית. ערכים חריגים יכולים להיות שגיאות, אבל גם תצפיות נדירות ולגיטימיות. תתעדו אותם, תבדקו את המקור, ותחליטו על בסיס ההקשר. לפעמים הנקודה הכי "מוזרה" בנתונים היא דווקא הכי מעניינת.

אודות הכותב Editorial Team

Our team of expert writers and editors.