LeetCodeを1日1題解いてみる (day35)

35日目に取り組んだ問題はこちら！

# 181. Employees Earning More Than Their Managers

https://leetcode.com/problems/employees-earning-more-than-their-managers/?lang=pythondata

## 問題文
Table: `Employee`
```
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| id          | int     |
| name        | varchar |
| salary      | int     |
| managerId   | int     |
+-------------+---------+
```
id is the primary key (column with unique values) for this table.
Each row of this table indicates the ID of an employee, their name, salary, and the ID of their manager.
 

Write a solution to find the employees who earn more than their managers.

Return the result table in any order.

## 僕の回答 (失敗作)
```py
import pandas as pd

def find_employees(employee: pd.DataFrame) -> pd.DataFrame:
    manager = employee[employee["managerId"] == None]
    employee = employee[employee["managerId"] != None]
    result_list = []
    for person in employee.itertuples():
        emp_salary = person["salary"]
        mana_salary = (manager[manager["id"] == person["managerId"]])["salary"]
        if mana_salary < emp_salary:
            print(person["name"])
            result_list.append(person.name)

    return pd.Dataframe({"name": result_list})
```

### 問題点
1. pandas のデータフレームでは、None を使う代わりに NaN が使われることが一般的。
`employee["managerId"] == None`ではなく、`employee["managerId"].isnull()`を使う。
2. itertuples() で返される行は、通常のタプルまたは名前付きタプルである。
`person["salary"]`のようにkeyでアクセスはできない。代わりに`person.salary`のように属性を指定する。
3. `mana_row["salary"]`はSeriesなので、`.iloc[0]`をつけて値を取り出す。

## 僕の回答の修正版 (まだ失敗作)
```py
import pandas as pd

def find_employees(employee: pd.DataFrame) -> pd.DataFrame:
    manager = employee[employee["managerId"].isnull()]
    employee = employee[employee["managerId"].notnull()]
    result_list = []
    for person in employee.itertuples():
        emp_salary = person.salary
        mana_salary = manager[manager["id"] == person.managerId]["salary"].iloc[0]
        if mana_salary < emp_salary:
            result_list.append(person.name)
    return pd.DataFrame({"employee": result_list})

```
しかしこれだと、誰かのmanagerだけど、さらにその上にmanagerがいる場合が対応できていない。

# 僕の回答 (成功作)
```py
import pandas as pd

def find_employees(employee: pd.DataFrame) -> pd.DataFrame:
    merged_df = pd.merge(employee, employee, left_on="managerId", right_on="id", how="left")
    result = merged_df[merged_df["salary_x"] > merged_df["salary_y"]]
    return (result.rename(columns={"name_x": "Employee"}))[["Employee"]]
```
## 他の回答例

```py
import pandas as pd

def find_employees(employee: pd.DataFrame) -> pd.DataFrame:
    joined = employee.merge(employee, how='left', left_on='managerId', right_on='id')
    return joined[joined['salary_y'] < joined['salary_x']][['name_x']].rename(columns={'name_x': 'Employee'})
```

## コメント
今回も学びが多かった。特に以下の内容は似て非なるものだったので面白い。
### pandas.merge() と DataFrame.merge() の違い

| **機能** | **pandas.merge()** | **DataFrame.merge()** |
|-----|-----|-----|
| **呼び出し方** | `pd.merge(left, right, ...)` | `left_df.merge(right=right_df, ...)` |
| **左側の DataFrame** | 引数 `left` で指定 | メソッドを呼び出したインスタンス（暗黙的）    |
| **記述の簡便性** | 左右の DataFrame を明示的に指定する必要あり | 呼び出し元の DataFrame が自動で `left` になる |
| **典型的な用途** | 汎用的に使用 | 特定の DataFrame に対する結合 |

## 次の問題はこちら!
https://qiita.com/h_y_s_d/items/3622c0204cccf58f8adc