# Transform — merge, groupby, agg, apply📖 שיעור

## מה זה?

יש לכם `orders.csv` ו-`customers.csv` נפרדים — איך רואים כל הזמנה **עם** שם הלקוח? `merge` פותר את זה (בדיוק כמו `$lookup` ב-MongoDB, או JOIN ב-SQL). ו"כמה הזמנות לכל לקוח, וסכום כולל?" — `groupby`+`agg` פותרים את זה (בדיוק כמו `GroupBy`+`Count`/`Sum` ב-LINQ, או `$group` ב-MongoDB). זהו הרעיון החוזר של הקורס כולו — כל טכנולוגיה עם התחביר שלה, אותה בעיה בדיוק.

## מילות מפתח שחשוב לזכור

• `merge()` — משלב שני DataFrames לפי עמודת מפתח משותפת — ה-JOIN של Pandas

• `groupby()` — מקבץ שורות לפי ערך עמודה, כהכנה לחישוב מצטבר על כל קבוצה

• `agg()` — מבצע חישוב מצטבר (סכום, ממוצע, ספירה) על קבוצות מ-`groupby`

• `apply()` — מריץ פונקציה מותאמת אישית על כל שורה/עמודה, כשאין פונקציה מובנית מתאימה

```python
# merge — JOIN between two DataFrames
orders = pd.read_csv("orders.csv")      # OrderId, CustomerId, Amount
customers = pd.read_csv("customers.csv") # CustomerId, Name

merged = orders.merge(customers, on="CustomerId")   # combine by shared key
# now every order also has the customer's Name
```

```python
# groupby + agg — grouping and aggregate computation
summary = orders.groupby("CustomerId").agg(
    TotalOrders=("OrderId", "count"),
    TotalSpent=("Amount", "sum")
).reset_index()
# for each customer: how many orders, and total amount spent — just like GroupBy+Count/Sum in LINQ
```

```python
# apply — a custom function applied to every row
def categorize(amount):
    if amount > 1000:
        return "VIP"
    elif amount > 100:
        return "Regular"
    return "Small"

orders["Category"] = orders["Amount"].apply(categorize)
# a new column, computed with custom logic
```

## הסבר עיקרי

`merge` דורש עמודת מפתח משותפת — `on="CustomerId"` אומר ל-Pandas "שלב שורות שבהן `CustomerId` זהה בשני ה-DataFrames" — בדיוק כמו FK ב-SQL, או `localField`/`foreignField` ב-`$lookup` של MongoDB. בלי עמודת מפתח תואמת, אין דרך לדעת אילו שורות "שייכות" יחד.

`groupby` לבד לא עושה כלום — צריך `agg` — `df.groupby("CustomerId")` בעצמו רק "מכין" את הקיבוץ (Object ביניים), לא מחשב עדיין שום דבר. `agg(...)` הוא מה שבפועל מבצע את החישוב על כל קבוצה — סכום, ממוצע, ספירה, או כמה חישובים בו-זמנית עם שמות ברורים (`TotalOrders=(...)`, `TotalSpent=(...)`).

`apply` הוא "מוצא אחרון" — כשיש פונקציה מובנית ל-Pandas (כמו `.sum()`, `.mean()`), היא כמעט תמיד מהירה יותר מ-`apply` עם פונקציה מותאמת-אישית, כי Pandas ממטב פעולות מובנות ברמה נמוכה. `apply` שימושי כשהלוגיקה מורכבת מכדי לבטא עם פעולות מובנות (כמו הקטגוריזציה בדוגמה) — אבל לא ברירת המחדל לכל דבר.

## יתרונות

`merge` משלב מקורות נתונים נפרדים בשורה אחת, בדיוק כמו JOIN ב-SQL; `groupby`+`agg` נותנים סטטיסטיקה לפי קבוצה בלי לולאות ידניות; `apply` נותן גמישות מלאה ללוגיקה מותאמת-אישית כשצריך.

## חסרונות

`merge` עם עמודות מפתח לא-תואמות (טיפוסים שונים, ערכים חסרים) יוצר שורות "יתומות" או ריקות בשקט; `apply` איטי משמעותית מפעולות Pandas מובנות על DataFrames גדולים — לא לבחור אותו כברירת מחדל.

## נקודות חשובות

• `merge(on=...)` הוא ה-JOIN של Pandas, מקביל ל-`$lookup`/SQL JOIN

• `groupby` לבד לא מחשב כלום — צריך `agg` אחריו לביצוע החישוב בפועל

• `apply` גמיש אך איטי יותר מפעולות מובנות — פתרון אחרון, לא ראשון

• זהו אותו רעיון (JOIN, GroupBy) שכבר נלמד ב-LINQ וב-MongoDB, רק תחביר שלישי

## טעויות נפוצות

• לצפות ש-`groupby` לבד "יעשה משהו" בלי `agg`/פונקציה מצטברת אחריו

• להשתמש ב-`apply` לחישובים פשוטים שיש להם פונקציה מובנית מהירה יותר (`sum`, `mean`)

• לשכוח שעמודת המפתח ב-`merge` חייבת טיפוס תואם בשני הצדדים (למשל `int` מול `string`) — אחרת לא מתאחדת נכון

## סיכום

`merge` משלב DataFrames לפי עמודת מפתח משותפת — ה-JOIN של Pandas. `groupby`+`agg` מקבצים ומחשבים סטטיסטיקה לפי קבוצה — `groupby` לבדו רק מכין, `agg` מבצע את החישוב בפועל. `apply` מריץ לוגיקה מותאמת-אישית על כל שורה, שימושי כשאין פונקציה מובנית מתאימה, אך איטי יותר ולכן לא ברירת מחדל. אלה שלושת הכלים המרכזיים לעיבוד נתונים אמיתי ב-Pandas.

## דוקומנטציה רשמית

[Pandas — Merge, join, concatenate](https://pandas.pydata.org/docs/user_guide/merging.html)

[Pandas — GroupBy](https://pandas.pydata.org/docs/user_guide/groupby.html)

---

## תרגילים

### תרגיל 1 — merge בסיסי

**המשימה:** צרו שני DataFrames קטנים (`orders`, `products`) עם עמודת מפתח משותפת (`ProductId`), ושלבו אותם עם `merge`.

**בדיקה:** התוצאה המשולבת מכילה עמודות משני המקורות, בשורה נכונה לכל הזמנה.

### תרגיל 2 — groupby+agg

**המשימה:** מתוך DataFrame הזמנות, קבצו לפי `ProductId` וחשבו סכום כמות (`sum`) וממוצע מחיר (`mean`) לכל מוצר.

**בדיקה:** כל מוצר מוצג עם סכום וממוצע נכונים, לא שורה בודדת ריקה.

---

## פרויקט מסכם

**המשימה:** בנו דוח "ביצועי מוצרים" משילוב `merge`+`groupby`+`agg`+`apply`.

**דרישות:**
1. `merge` בין `orders` ל-`products` לפי `ProductId`
2. `groupby("ProductName")` עם `agg` — סכום כמות שנמכרה וסך הכנסה
3. `apply` שמוסיף עמודת `Tier` ("רב-מכר" אם סך הכנסה מעל סף מסוים, אחרת "רגיל")

**בדיקה:** הדוח הסופי מציג לכל מוצר את שמו, סטטיסטיקת המכירות, וה-Tier הנכון לפי הסף שהוגדר.

## מה בפרק הבא

בפרק הבא — **ניקוי נתונים**: `fillna`, `dropna`, `rename`, `to_datetime` — איך מטפלים בנתונים חסרים ולא-אחידים לפני שהם "מזהמים" ניתוח.
