今さら聞けないPython - pandasを用いたデータ分析

こちらのウェビナーで説明した内容の抜粋です。最初のセクションの「pandasを用いたデータ分析」をカバーしています。[今さら聞けないPython \- Pythonの基礎](https://qiita.com/taka_yayoi/items/bb96e1be88959902cb17)の続きです。

https://pages.databricks.com/202303-APJ-VE-Fundamental-Series-Python.html

全体構成は以下の通りです。
- [今さら聞けないPython \- Pythonの基礎](https://qiita.com/taka_yayoi/items/bb96e1be88959902cb17)
- [今さら聞けないPython \- pandasを用いたデータ分析](https://qiita.com/taka_yayoi/items/3bb88b204f47bdf091eb)
- [今さら聞けないPython \- scikit\-learnを用いた機械学習](https://qiita.com/taka_yayoi/items/08d3166714b53b9acc7b)
- [今さら聞けないPython \- Sparkのご紹介](https://qiita.com/taka_yayoi/items/5415db284b96c7c60d44)

ウェビナーで使用したノートブックはこちらにあります。

https://github.com/taka-yayoi/public_repo_2/tree/master/python_webinar

本記事でカバーしているノートブックはこちらです。

https://github.com/taka-yayoi/public_repo_2/blob/master/python_webinar/2.%20pandas%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E3%83%87%E3%83%BC%E3%82%BF%E5%88%86%E6%9E%90.py

- `pandas`を使う動機づけ
 - `pandas`とその歴史
 - COVID-19データセットのインポート
   * `pd.read_csv()`
 - データの要約
   * `head`, `tail`, `shape`
   * `sum`, `min`, `count`, `mean`, `std`
   * `describe`
 - データのスライスと加工
   * スライス, `loc`, `iloc`
   * `value_counts`
   * `drop`
   * `sort_values`
   * フィルタリング
 - データのグルーピングおよび集計関数の実行
   * `groupby`
 - 欠損値、重複への対応
   * `isnull`
   * `unique`, `drop_duplicates`
   * `fillna`
 - 可視化
   * ヒストグラム
   * 散布図
   * 折れ線グラフ

# `pandas`を使う動機づけ

大きな絵からスタートしましょう...<br><br>

* 人類は道具を使う動物です
* コンピューターは我々が作り出した最もパワフルなツールの一つです
* コードを書くことで、これらのツールのフルパワーを解き放つことができます

Ok、クールだね。でもなぜ`pandas`?<br><br>

* これまで以上にデータは意思決定において重要となっています。
* Excelは素晴らしいものですが、もし...
  - 毎日新規のデータに対して再実行できるように分析を自動化したいとしたら？
  - 同僚と共有できるようにコードベースを構築したいとしたら？
  - ビジネス上の意思決定につながるより堅牢な分析を必要としたら？
  - 機械学習を行いたいとしたら？
* Pythonにおけるデータ分析やデータサイエンティストによって使用されるコアのライブラリの一つが

    `pandas`にようこそ...

# `pandas`とその歴史

`pandas`は、Pythonプログラミング言語上で開発された、高速、パワフル、柔軟かつ、簡単に使用できるオープンソースのデータ分析、データ操作ツールです。

ハイライト:

- 2008年に開発され、2009年にオープンソース化されました。
- インデックスがインテグレーションされたデータ操作のための高速かつ効率的な**データフレームオブジェクト**
- インメモリーのデータ構造と様々なフォーマット間の**データ読み書き**ツール: CSV、テキストファイル、Microsoft Excel、SQLデータベース、高速なHDF5フォーマット。
- インテリジェントな**データアライメント**とインテグレーションされた**欠損データ**への対応: 計算時に自動でラベルベースのアライメントを取得し、汚いデータを容易に綺麗な状態に変換。
- データセットに対する柔軟な **リシェイプとピボット**。
- 大規模データセットに対するインテリジェントなラベルベースの **スライス、ファンシーインデックス、サブセット作成**。
- **サイズ可変性**を持つデータ構造に対するカラムの追加・削除。
- パワフルな**group by**を用いたデータの集計、変換によるデータセットに対するsplit-apply-combineオペレーション。
- データセットに対する高性能の**マージとジョイン**。
- 階層型軸**インデックス**による、低次元データ構造における直感的な高次元データ操作手段の提供。
- **時系列**機能: 日付範囲の生成、頻度変換、移動ウィンドウ統計情報、日付シフト、ラギング。ドメイン固有の時間オフセットの作成、データ損失なしの時系列データのジョインもサポート。
- Cython、Cで記述されたクリティカルなコードパスにより高度に**最適化された**パフォーマンス。
- Pythonとpandasは、金融、神経科学、経営、統計学、広告、Web分析などを含むさまざまな**学術、商用の領域**で活用されています。

# COVID-19データセットのインポート

フォルダー構造を検索するために `%sh ls` を使います。`%sh`はDatabricksノートブックでシェルコマンドを実行することができる[マジックコマンド](https://qiita.com/taka_yayoi/items/dfb53f63aed2fbd344fc#%E3%83%9F%E3%83%83%E3%82%AF%E3%82%B9%E8%A8%80%E8%AA%9E)です。

```
%sh ls /dbfs/databricks-datasets/COVID/
```

```
CORD-19
CSSEGISandData
ESRI_hospital_beds
IHME
USAFacts
coronavirusdataset
covid-19-data
download_daily_covid-19_datasets.sh
estimated_patient_impact_and_hospital_capacity_by_state
```


```
%sh ls /dbfs/databricks-datasets/COVID/CSSEGISandData/csse_covid_19_data/csse_covid_19_daily_reports
```

```
01-01-2021.csv
01-02-2021.csv
01-03-2021.csv
01-04-2021.csv
01-05-2021.csv
01-06-2021.csv
01-07-2021.csv
01-08-2021.csv
01-09-2021.csv
01-10-2021.csv
```

CSVファイルの最初の数行を表示するために `%sh head` を使います。

```
%sh head /dbfs/databricks-datasets/COVID/CSSEGISandData/csse_covid_19_data/csse_covid_19_daily_reports/04-11-2020.csv
```
```
FIPS,Admin2,Province_State,Country_Region,Last_Update,Lat,Long_,Confirmed,Deaths,Recovered,Active,Combined_Key
45001,Abbeville,South Carolina,US,2020-04-11 22:45:33,34.22333378,-82.46170658,9,0,0,0,"Abbeville, South Carolina, US"
22001,Acadia,Louisiana,US,2020-04-11 22:45:33,30.295064899999996,-92.41419698,98,4,0,0,"Acadia, Louisiana, US"
51001,Accomack,Virginia,US,2020-04-11 22:45:33,37.76707161,-75.63234615,15,0,0,0,"Accomack, Virginia, US"
16001,Ada,Idaho,US,2020-04-11 22:45:33,43.4526575,-116.24155159999998,513,6,0,0,"Ada, Idaho, US"
19001,Adair,Iowa,US,2020-04-11 22:45:33,41.33075609,-94.47105874,1,0,0,0,"Adair, Iowa, US"
```

`pandas`をインポートします。別名を`pd`とします。

```py:Python
import pandas as pd
```

CSVファイルを読み込みます。これによって`データフレーム`が生成されます。1行目のコマンドでは[ファイルシステムユーティリティ](https://qiita.com/taka_yayoi/items/3717623187859809515d#cp%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89dbutilsfscp)を用いてファイルをローカルにコピーしています。

```py:Python
dbutils.fs.cp("dbfs:/databricks-datasets/COVID/CSSEGISandData/csse_covid_19_data/csse_covid_19_daily_reports/04-11-2020.csv", "file:/tmp/covid.csv")
pd.read_csv("file:/tmp/covid.csv")
```
![Screenshot 2023-03-28 at 16.37.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/95821c11-402d-0210-c43c-659231c4de54.png)

それでは数行のコードを組み合わせて、再利用できるように`データフレーム`を保存してみましょう。

```py:Python
import pandas as pd

df = pd.read_csv("file:/tmp/covid.csv")

df
```
![Screenshot 2023-03-28 at 16.37.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/95821c11-402d-0210-c43c-659231c4de54.png)

# データの要約

データの最初の数行と最後の数行を見てみましょう。

```py:Python
df.head()
```
![Screenshot 2023-03-28 at 16.43.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/04419e1e-0ac9-9546-88ac-b5b4c4f51e11.png)

```py:Python
df.tail(2)
```
![Screenshot 2023-03-28 at 16.44.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/df367050-94fc-f160-2f7c-e912d98003e7.png)

データセットは何行でしょうか？

```py:Python
df.shape
```
```
Out[10]: (2967, 12)
```

データを要約しましょう。

```py:Python
# df.sum()
# df.min()
# df.max()
# df.count()
df.mean()
# df.std()
```
```
Out[11]: FIPS         31172.744602
Lat             36.564846
Long_          -80.663000
Confirmed      584.822042
Deaths          36.747893
Recovered      135.600944
Active         259.575666
dtype: float64
```

これらのサマリー統計情報を集計して表示することができます...

```py:Python
df.describe()
```
![Screenshot 2023-03-28 at 16.48.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/ec54b51c-6f69-a098-58e2-d8a24e485cb4.png)

# データのスライスと加工

感染者数だけを取り出します。

```py:Python
df['Confirmed']
```
```
Out[13]: 0         9
1        98
2        15
3       513
4         1
       ... 
2962    268
2963      1
2964     40
2965     14
2966      0
Name: Confirmed, Length: 2967, dtype: int64
```

国と感染者数を取り出します。その前に、データフレームの列名を取得します。

```py:Python
df.columns
```
```
Out[14]: Index(['FIPS', 'Admin2', 'Province_State', 'Country_Region', 'Last_Update',
       'Lat', 'Long_', 'Confirmed', 'Deaths', 'Recovered', 'Active',
       'Combined_Key'],
      dtype='object')
```

```py:Python
df[['Country_Region', 'Confirmed']]
```
![Screenshot 2023-03-28 at 16.57.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/0deaaf1b-fbab-d474-0d1a-6e409462b59a.png)

新たなカラム `Date` を作成します。

```py:Python
import datetime

df["Date"] = datetime.date(2020, 4, 11)

df["Date"].head()
```
```
Out[17]: 0    2020-04-11
1    2020-04-11
2    2020-04-11
3    2020-04-11
4    2020-04-11
Name: Date, dtype: object
```

データフレームの最初の11行を取り出してスライスします。

```py:Python
df.loc[:10, ['Country_Region', 'Confirmed']]
# df.loc[0:10, ['Country_Region', 'Confirmed']] # 同じ処理です
```
![Screenshot 2023-03-28 at 17.00.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/7888f5ba-4a69-5598-e4c2-906d2448e4fb.png)

最初の行の最初のカラムだけを返却します。

```py:Python
df.iloc[0, 0]
```
```
Out[19]: 45001.0
```

国ごとにいくつの地域があるのでしょうか？`value_counts`を使用して、値ごとのカウントをとります。

```py:Python
df["Country_Region"].value_counts()
```
```
Out[20]: US                2702
China               33
Canada              16
United Kingdom      12
France              11
                  ... 
Georgia              1
Germany              1
Ghana                1
Greece               1
Zimbabwe             1
Name: Country_Region, Length: 185, dtype: int64
```

FIPSとはなんでしょうか？ここでは不要なので削除します。`drop`の引数`axis=1`は列方向であることを指定しています。

```py:Python
df = df.drop("FIPS", axis=1)
```

感染者数でソートします。引数`ascending=False`は降順を指定しています。

```py:Python
df.sort_values("Confirmed", ascending=False)
```
![Screenshot 2023-03-28 at 17.09.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/9c67b3b6-fe63-b34f-28d2-42bf16937543.png)

アメリカ(US)で起きていることだけを見てみましょう。

```py:Python
df[df["Country_Region"] == "US"]
```
![Screenshot 2023-03-28 at 17.10.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/0d8e3241-1a25-d073-dd6c-35208ae9106c.png)

# データのグルーピングと集計関数の実行

感染者数が最も多い国はどこでしょうか？データをグルーピングし合計を計算します。**集計関数はスカラー値(単一の値)を返却することに注意してください。**

`Country_Region`をキーにグルーピングを行い、グループごとの感染者数`Confirmed`
の合計を`sum()`で取得し、感染者数合計の降順で表示します。

```py:Python
df.groupby("Country_Region")["Confirmed"].sum().sort_values(ascending=False)
```
```
Out[26]: Country_Region
US                  526764
Spain               163027
Italy               152271
Germany             124908
France               93909
                     ...  
South Sudan              4
Timor-Leste              2
Papua New Guinea         2
Yemen                    1
Samoa                    0
Name: Confirmed, Length: 185, dtype: int64
```

感染者数が最も多いUSの州はどこでしょうか？フィルタリング条件を追加し、`Province_State`ごとに集計します。

```py:Python
df[df['Country_Region'] == "US"].groupby("Province_State")["Confirmed"].sum().sort_values(ascending=False)
```
```
Out[27]: Province_State
New York                    181026
New Jersey                   58151
Michigan                     23605
Massachusetts                22860
Pennsylvania                 21719
California                   21711
Louisiana                    20014
Illinois                     19180
Florida                      18494
Texas                        13145
Georgia                      12159
Connecticut                  11510
```

# 欠損データと重複への対応

null値はあるのでしょうか？

```py:Python
df.isnull().tail()
```
![Screenshot 2023-03-28 at 17.16.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/4df869b8-94b5-aa0b-8762-c30d893a5b2a.png)

`sum()`でnull値の数を取得することができます。

```py:Python
df.isnull().sum()
```
```
Out[29]: Admin2            272
Province_State    181
Country_Region      0
Last_Update         0
Lat                58
Long_              58
Confirmed           0
Deaths              0
Recovered           0
Active              0
Combined_Key        0
Date                0
dtype: int64
```

```py:PYthon
df['Country_Region'].unique().shape
```
```
Out[30]: (185,)
```

null値を特定の値で置き換えることもできます。

```py:Python
df.fillna("NO DATA AVAILABLE").tail(3)
```

# 可視化
   * ヒストグラム
   * 散布図
   * 折れ線グラフ

```py:Python
import matplotlib.pyplot as plt

%matplotlib inline

us_subset_df = df[df["Country_Region"] == "US"]
```

USの州と地域ごとの死者数の _分布_ はどうなっているのでしょうか？

```py:Python
us_subset_df.groupby("Province_State")["Deaths"].sum().hist(bins=30)
```
![Screenshot 2023-03-28 at 17.25.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/933bbe44-a5d2-4ee4-6eff-936ef3a75419.png)

死者数に比べて感染者数はどうなっているのでしょうか？

```py:Python
us_subset_df[us_subset_df["Deaths"] < 1000].plot.scatter(x="Confirmed", y="Deaths")
```
![Screenshot 2023-03-28 at 17.26.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/bdcfa606-469d-5cd9-e6ba-6106d1c95160.png)

複数の日のデータをインポートします。

```py:Python
src_path_base = "dbfs:/databricks-datasets/COVID/CSSEGISandData/csse_covid_19_data/csse_covid_19_daily_reports/"
dest_path_base = "file:/tmp/covid_daily_reports/"

files = [
 '11-21-2020.csv',
 '11-22-2020.csv',
 '11-23-2020.csv',
 '11-24-2020.csv',
 '11-25-2020.csv',
 '11-26-2020.csv',
 '11-27-2020.csv',
 '11-28-2020.csv',
 '11-29-2020.csv',
 '11-30-2020.csv'
]

dfs = []

for file in files:
  filename = dest_path_base+file
  dbutils.fs.cp(src_path_base+file, filename)
  
  temp_df = pd.read_csv(filename)
  temp_df.columns = [c.replace("/", "_") for c in temp_df.columns]
  temp_df.columns = [c.replace(" ", "_") for c in temp_df.columns]
  
  month, day, year = filename.split("/")[-1].replace(".csv", "").split("-")
  d = datetime.date(int(year), int(month), int(day))
  temp_df["Date"] = d

  dfs.append(temp_df)
  
all_days_df = pd.concat(dfs, axis=0, ignore_index=True, sort=False)
all_days_df = all_days_df.drop(["Lat", "Long_", "FIPS", "Combined_Key", "Last_Update"], axis=1)
```

時間と共に感染病はどのように広がったのでしょうか？

```py:Python
all_days_df.groupby("Date")["Confirmed"].sum().plot(title="Confirmed Cases over Time", rot=45)
```
![Screenshot 2023-03-28 at 17.27.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/2f2bdf80-33fc-1b94-3e0d-68431e0b519c.png)

これをケースのタイプごとにブレークダウンします。

```py:Python
all_days_df.groupby("Date")["Confirmed", "Deaths", "Recovered"].sum().plot(
    title="Confirmed, Deaths, Recovered over Time", rot=45
)
```
![Screenshot 2023-03-28 at 17.28.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/f47c4527-1632-4194-fa2e-1fd37c4a71d7.png)

特定の場所における増加状況はどうなっているでしょうか？

```py:Python
(
    all_days_df[
        (all_days_df["Country_Region"] == "US")
        & (all_days_df["Province_State"] == "California")
        & (all_days_df["Admin2"] == "San Francisco")
    ]
    .groupby("Date")["Confirmed", "Deaths", "Recovered"]
    .sum()
    .plot(title="Confirmed, Deaths, Recovered over Time", rot=45)
)
```
![Screenshot 2023-03-28 at 17.29.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/eef6c310-36f1-cbbb-5e8f-bf7daf020aed.png)

このように、pandasデータフレームを活用することで柔軟にデータ分析を行うことができます。

[今さら聞けないPython \- scikit\-learnを用いた機械学習](https://qiita.com/taka_yayoi/items/08d3166714b53b9acc7b)に続きます。

### Databricksクイックスタートガイド

[Databricksクイックスタートガイド](https://www.amazon.co.jp/dp/B09V1YXFVQ/)


### Databricks無料トライアル

[Databricks無料トライアル](https://databricks.com/jp/try-databricks)