pyてよn日記

一寸先は闇が人生

Python:Pandas チートシート

最近,Python のデータ処理のためのライブラリ Pandas についてキャッチアップしているので,その備忘録として自分用チートシートを作ってみた.項目については ↑ の目次を見てください.

各種参考リンク

本記事は以下のリンクを参考に自分なりにまとめている(恐らく Pandas の大体は網羅できる.当然筆者は全部見れてない).

Pandas で困ったら,それっぽい英単語を並べてググると Stackoverflow のどんぴしゃな質問が結構引っ掛かってくれる.日本語で出てこなかったら上手く英語を使うと良い(むしろ始めに英語で検索する方が時短になることが多い.これは Pandas に限らず.).

表形式のデータの構成要素

Pandas では主に表形式のデータを扱う.

表形式のデータは「テーブル」(table,特にデータ自体を指す時は「テーブルデータ」とも呼ぶ)と呼ばれるが,テーブルは 3 つの要素,「ヘッダ」(header),「カラム」(column),「レコード」(record)で構成される.

  • テーブル(テーブルデータ)
    • ヘッダ:各列が何を表すか.項目.
    • カラム:各列のこと
    • レコード:各行のこと.DB にちなんで「レコード」と呼ばれる.

Pandas の主要なデータ型:pd.Series と pd.DataFrame

基本

Pandas では,テーブルデータを pd.DataFrame という Pandas 特有の表形式のデータ型に格納して操作する.そして,pd.DataFrame から各行,各列,を取り出したデータを pd.Series というデータ型として扱う.

pd.DataFrame は,ヘッダ(pd.Series とは別の pd.Index というヘッダを扱うためのデータ型),pd.Series から成る.

  • pd.DataFrame の構成要素
    • pd.Index
      • ヘッダ
    • pd.Series
      • pd.DataFrame の各レコードやカラムを切り取ったデータを表すデータ型.
      • 縦横,両方向に切り取ってもどちらも pd.Series になるため注意!
      • 単独でも扱える.

Pandas を用いてデータを作る

pd.Series

基本

pd.Series は,主に以下のデータ型を元に作成することができる.

  • リスト
  • 辞書
  • np.ndarray

引数は以下の通り.

pd.Series(
    data=None,  # <-- この引数にリスト,辞書,np.ndarray を渡す
    index=None,
    dtype=None,
    name=None,
    copy=False,
    fastpath=False,
)

ポイントは以下の通り.

  • 引数 data に渡すデータの型によって生成される pd.Series のインデックスの値が異なる
    • リスト,np.ndarray を渡した場合
      • 0-origin で各要素にインデックスが付与される
      • 引数 index を渡すとそのインデックスが付与される
    • 辞書を渡した場合
      • 辞書の key が各 value のインデックスになる.

リスト,辞書,np.ndarray から pd.Series を作る

リスト,辞書,np.ndarray から pd.Series を作る.

data_list = ['John', 'male', 22]
data_dict = {
    'name': 'John',
    'sex': 'male',
    'age': 22,
}
data_ndarray = np.array(data_list)

end_str = '\n----------\n'
print(pd.Series(data_list), end=end_str)
print(pd.Series(data_dict), end=end_str)
print(pd.Series(data_ndarray))

出力

0       {'name': 'John', 'sex': 'male', 'age': 22}
1       {'name': 'Zack', 'sex': 'male', 'age': 30}
2    {'name': 'Emily', 'sex': 'female', 'age': 32}
dtype: object
----------
name    John
sex     male
age       22
dtype: object
----------
0    John
1    male
2      22
dtype: object

それぞれのデータ型を pd.Series に渡した時の挙動を把握しておくと良い.ちなみに,np.ndarray は配列内の要素が同じ型であるため,異なるデータ型が混ざったリストを渡すと配列が生成するときに暗黙のキャストが起こる.上記の場合,str 型と int 型が混ざっていたため,それらの親クラス(上位のクラス)である object 型にキャストされている.Pandas でテーブルデータを扱う際は,各列,各行のデータ型を意識すると効率よくデータを扱えるようになる.

pd.DataFrame

基本

pd.DataFrame は,pd.Series などさまざまなデータ型を元に作ることができるが,主に,以下の 3 つが用いられる.

  • pd.DataFrame の元になるデータ
    • np.ndarray から作る
    • 辞書から作る
    • CSV,TSV などの外部ファイルを読み込んで作る

np.ndarray から作る

arr = np.random.randint(0, 5, size=(3, 7))
pd.DataFrame(data=arr)

出力

    0 1 2 3 4 5 6
-----------------
0   4 2 0 2 4 1 4
1   3 4 3 3 0 4 1
2   4 0 2 0 1 1 4

辞書から作る

辞書を要素に持つリストから,key をヘッダとする DataFrame が作れる.

data1 = {
    'name': 'John',
    'sex': 'male',
    'age': 22,
}

data2 = {
    'name': 'Zack',
    'sex': 'male',
    'age': 30,
}

data3 = {
    'name': 'Emily',
    'sex': 'female',
    'age': 32,
}

data_list = [data1, data2, data3]

pd.DataFrame(data=data_list)

出力

    name sex age
----------------
0   John male 22
1   Zack male 30
2   Emily female 32

columns と index を指定して DataFrame を作る

  • columns:各列のラベル.ヘッダ.
  • index:各行のラベル
columns = list('abcdefg')  # ヘッダ
index = np.arange(3)  # 行のラベル

pd.DataFrame(data=arr, columns=columns, index=index)

なお,columns や index を省略した場合,0-origin の column,index が自動で補完される.

(重要)外部ファイルからデータを読み込む

恐らく Pandas でデータを操作する際に最も行われる方法.CSV,TSV,JSONExcel ファイルなど,様々な形式のファイルを pd.DataFrame として読み込むことができる. 使うのは pd.read_**** には特定のファイル形式が入る)という関数.

  • CSVpd.read_csv()
  • TSV:pd.read_tsv()
  • JSONpd.read_json()
  • Excelpd.read_excel()
filepath = '/path/to/file'

# csv ファイルを読み込む
df = pd.read_csv(filepath)
print(df.shape)

pd.DataFrame の基本的な操作

DataFrame の基本情報を取得する

  • df.info():「有効データ数」「データ型」「メモリ使用量」などの総合的な情報を表示
  • df.count():各カラムの有効データ数を表示
  • df.shape:行数,列数のを取得(行列の形)
  • df.dtypes:各カラムのデータ型を表示

行数,列数を取得:DataFrame.shape

(行数, 列数) というタプルが返される.

df.shape

最初,最後の数行を取得:DataFrame.head()DataFrame.tail()

引数 n で何行表示するかを設定できる.デフォルトは 5 行.

  • df.head():最初の数行
# DataFrame の最初の 5 行を表示
df.head()

# DataFrame の最初の 100 行を表示
df.head(n=100)
  • df.tail():最後の数行
# DataFrame の最後の 5 行を表示
df.tail()

# DataFrame の最後の 100 行を表示
df.tail(n=100)

インデックス,カラムを取得:DataFrame.indexDataFrame.columns

  • df.index:インデックス(行ラベル)を取得
df.index
  • df.columns:カラム(列ラベル)を取得
df.columns

行列を np.ndarray として取得:DataFrame.values

pd.DataFrame のままより,np.ndarray で計算を回した方が高速になる場合がある.

df = pd.read_csv('./input/train.csv')
print(type(df))
print(df.shape)  # shape は当然同じ

arr = df.values
print(type(arr))
print(arr.shape)  # shape は当然同じ

特定の行,列,行列を抽出する

DataFrame に対して,以下のような操作を行うことで行,列,行列を抽出できる.

行・列の抽出

抽出方法によって戻り値が異なるため注意.

  • 戻り値:pd.Series
    • df[カラム1]:特定のカラムを抽出.
  • 戻り値:pd.DataFrame
    • df[[カラム1, カラム2, ...]]
    • df.iloc[]:Pythonic なインデックス(整数インデックスによるスライスなど)による抽出
    • df.loc[]:行名,カラム名を使った抽出

行(Observations)の抽出

  • df.query()
    • 条件に引っ掛かったもの「だけ」を抽出
    • 検索コストが高い(複数の Series を跨ぐため,パフォーマンスに難あり)
  • df.where()
    • shape が保たれる
    • 条件に引っ掛かった行はそのまま,それ以外は NaN になる
    • データの結合時(正直あまり把握してない)

DataFrame.query

  • 公式ドキュメント

pandas.pydata.org

  • note.nkmk.me:pandas.DataFrameの行を条件で抽出するquery

note.nkmk.me

  • query のパフォーマンスについて

ohke.hateblo.jp

クエリの評価エンジン

df.query() の引数,engine で評価エンジンを指定できる

  • クエリの評価エンジン
    • デフォルトは None
    • numexpr:クエリが df.eval で評価される
    • python:クエリが Python で評価される
基本

シングル / ダブルクォーテーションの使い分けに注意.

df.query('[column] > 10')
df.query("[column1] > 10 & [column2] == 'foo'")
df.query("[column1] > 10 & [column2] == 'foo'")
null の判定
df.query('[column1].isnull()', engine='python')
df.query("[column1].isnull() & [column2] == 'foo'", engine='python')

DataFrame.where

TODO

列(変数)の抽出

TODO


要素・行・列に関数を適用する:map,applymap,apply

note.nkmk.me


インデックスの操作

インデックスの操作を取り上げる

DataFrame.reset_index() で再度 index を割り振る

DataFrame.set_index() で特定のカラムを index に変換する


欠損値(NaN)の処理

「統計的に意味のある処理」ではなく,NaN を機械的に処理するという意味での処理.

欠損値の判定:DataFrame.isna()

基本

df.isna()

DataFrame.isna() を用いて,DataFrame に含まれる欠損値の割合を把握する

DataFrame にどの程度欠損値が含まれているか,以下のコードで一発で把握できる. 欠損値を判定し,列方向に足し合わせると各カラムの欠損値の数が算出される.それを行数で割って 100 を掛ければ各カラムに含まれる欠損値の割合(%)が算出される.逆に 1 からそれらを引けば,各カラムの有効なデータの割合(欠損値でない行数)を算出できる.

  • 各カラムの欠損値の割合(%)
(df.isna().sum(axis=0) / df.shape[0]) * 100
  • 各カラムの有効なデータの割合(%)
(1 - (df.isna().sum(axis=0) / df.shape[0])) * 100

欠損値の除去:DataFrame.dropna()

NaN がある「行」を drop(消去)する.かなり使う関数だが,使い方がいろいろあってややこしい. ちなみに戻り値は「消去後の DataFrame」であり,元の DataFrame を変更しない非破壊的な操作である.

基本

df.dropna()

axis による除去方向の指定

  • df.dropna(axis=0):デフォルト.NaN を含む行を消去.
  • df.dropna(axis=1):NaN を含む列を消去

shape を見ると除去の違いが分かる.

print(df.shape)
print(df.dropna(axis=0).shape)  # 行数が減る
print(df.dropna(axis=1).shape)  # 列数が減る

全ての行が欠損していたらその行を落とす

df.dropna(how='all')

引数 subset を用いた欠損値の除去

df.dropna()subset という引数にカラム名を渡すことで,「そのカラムにおいて NaN を含む行を消去する」という操作が行える.

実業務でデータ処理をしてると,当たり前のように NaN ばかりになる.もし csv やエクセルでデータ作ったら,空欄は全部 NaN になる. そうなると,特定のカラムにおいて NaN が含まれる行だけを落とす必要が出てくる

subset 引数による「特定のカラムにのみ NaN が含まれたらその行を落とす」のはとても便利なため覚えておくと良い. あと,リスト形式で渡すのには注意.たとえ 1 つのカラムでも,リストで渡す

drop_target = ['Age', 'Pclass']
df.dropna(axis=0, subset=drop_target)

以上の処理は,「Age,または Pclass のカラムに NaN が含まれた場合,その行を消去する」という操作である.他のカラムで NaN が含まれていたとしても,その行は消去されない.

欠損値を特定の値で埋める:DataFrame.fillna(value)

0 埋め

df.fillna(0)

文字埋め

df.fillna('This is a pen.')

直前の行の値で欠損値を埋める

'ffill' は forward fill(前方埋め)を表す.その欠損値の前の行の値で埋められる

df.fillna(method='ffill')

応用例:欠損値をそのカラムの平均値で埋める

age_ave = df['Ave'].mean()
df['Age'] = df['Ave'].fillna(age_ave)


ここからが重要.

データの要約

各カラムの集計関数

以下に,各カラムの集計値を算出する DataFrame のメソッドを列挙した.

  • df.sum()
  • df.count()
  • df.min()
  • df.max()
  • df.mean()
  • df.var()
    • df.var(ddof=0)
    • df.var(ddof=1)
  • df.std()
    • df.std(ddof=0)
    • df.std(ddof=1)
  • df.median()
  • df.quantile([0.25, 0.75])
  • df.apply()
    • これだけ毛色が異なる

DataFrame 全体の集計値:df.describe()

  • df.describe()
    • 数値データに対して各種統計量を計算
  • df.describe(include='all')
    • (カテゴリカルデータを含む)全てのデータに対して各種統計量を計算

データの整形

データのグループ化:groupby

特定のカラムでグルーピングする.

単一カラムで groupby

基本
df.groupby('column1').mean()
df.groupby(['column1']).mean()
各グループの統計量を取得

カラム 1 でグループ分けをした後,各グループの各カラムの集計は以下で行える.ただし,これは非常に見辛い.

df.groupby('column1').describe()

.describe() の戻り値は DataFrame であるため,[]カラム名を指定することで,「グルーピング後の各カラムの統計量」を取得できる.

df.groupby('column1').describe()['column2']

複数カラムで groupby

index は MultiIndex になる.各 index は (column1, column2) というタプルになる.

df.groupby(['column1', 'column2'])

groupby 後の MultiIndex を解除

for 文で各グループの DataFrame を取り出す

groupby の戻り値はイテレータであるため,その結果を for 文で取り出すことができる. 具体的には,(index, groupby でグルーピングされた特定のグループの DataFrame) というタプルが返る. これにより,各グループの DataFrame を取り出すことができる.

タプルの index には,groupby に指定したカラムの unique を取ったものが順番に取り出される,例えばタイタニックの Pclass なら 1, 2, 3 の 3 つである(つまり 3 ループ). つまりは,for 文 1 ループにつき 1 つのグループの情報を取り出すことができるということ.

for idx, df_group in df.groupby('column1'):
    print(f'type: {type(df_group)}')  # データ型は DataFrame
    print(f'group: {idx}')
    print(df_group.describe())

ピボットテーブル:pivot_table

DataFrame.pivot_table を用い,データの集計を行うことができる.特に,整然データを集計するのに用いられる.

縦持ちのデータを横持ちにする

データ処理の世界では、データの持ち方に縦持ちと横持ちという考え方がある。 縦持ちでは、レコードに種類といったカラムを持たせてデータを追加していく。 それに対し横持ちでは種類ごとにカラムを用意した上でデータを追加する形を取る。 一般的にはデータの持ち方としては縦持ちのものが多いと思う。

(引用元:[Python: pandas で手持ちのデータを横持ちにする](https://blog.amedama.jp/entry/2018/06/03/124421

blog.amedama.jp

注意点:pivot_table 実行後にインデックスが MultiIndex になる

pivot_table の引数 indexcolumns に渡す引数としてリスト型を渡すと必ず MuiltiIndex になる.1 つの要素を持つリストでも同様である.

  • 集計対象のカラムをリストとして渡す
    • df.index,df.columns が MultiIndex になる
df = df_billing.pivot_table(
    index=['day'],
    columns=['service'],
    values=['cost_per_day'],
    aggfunc='sum',
    fill_value=0.0
)
  • 集計対象のカラムを文字列型で渡す
    • df.index,df.columns が(MultiIndex)ではない通常の Index になる
df = df_billing.pivot_table(
-    index=['day'],
+    index='day',
-    columns=['service'],
+    columns='service',
-    values=['cost_per_day'],
+    values='cost_per_day',
    aggfunc='sum',
    fill_value=0.0
)

テーブルの結合:merge,join,concat

merge

[TODO]標準的な結合

結合の方向があるため注意.




[TODO]フィルタリング結合:マッチする/しない行を結合

[TODO]集合ライクな結合

集合ライクな結合,つまり,論理和論理積排他的論理和のような演算によるテーブルの結合が行える.

階層インデックス MultiIndex の使い方

Multiindex を特定の階層で平坦化する

df.columns = df.columns.get_level_values(level)
df.columns = df.columns.get_level_values(0)
df.columns = df.columns.get_level_values(1)

[TODO]データ分析の tips

列データの前処理のテクニック

www.soudegesu.com

整数型のカラムの 0-padding:zfill

  • str 型にキャストして zfill
digit = 5  # 5 -> '00005'
df['id'].astype('str').str.zfill(digit)

カテゴリ変数をワンホットエンコード:pd.get_dummies

  • カテゴリ変数
    • カテゴリを数値で表しているデータ
    • ex)
      • 性別の (男, 女, その他) = (0, 1, 2)
      • 果物 (リンゴ, バナナ, ブドウ, ...) = (0, 1, 2, ...)
  • ワンホットエンコード
    • カテゴリを表現するカラムを,それぞれのカテゴリ毎の列に分解すること

引数 prefix は列を分解した後にその列のカラム名の prefix となる.分解後の列は,そのカテゴリに属するか否かを表す bool 型が格納される.

import pandas as pd

pd.get_dummies(df['性別'], prefix='sex')

2 進数の文字列が入ったカラムを複数のカラムに分解する:DataFrame.from_records

  • ex) アンケートの 1 つの質問の中で,複数回答が可能なアンケートのデータを 1 列で表現している場合のデータ
    • ex1) 好きな科目を 1 で表す:国数英理社 -> 0011011100
import pandas as pd

pd.DataFrame.from_records(df['好きな科目'], columns=['国語', '数学', '英語', '理科', '社会'])
# '00110' がそれぞれの科目の列に分解される

[TODO]時系列データの処理

系列データの基本処理

タイムゾーンの設定


[TODO]メソッドチェーン

[TODO]window 関数

DataFrame.expanding()

要約関数を累積的に適用可能にした Expanding オブジェクトを返す.

df.expanding()

DataFrame.rolling(n)

長さ n の window に要約関数を適用可能にした Rolling オブジェクトを返す.

df.rolling(n)

プロット(描画)

DataFrame.plot メソッドを利用すると,matplotlib,plotly 等を経由せずに,DataFrame からデータのプロット(描画)を直接行える.

ヒストグラム

df.plot.hist()

散布図(scatter plot)

xy はプロットしたいカラム名を指定.「特定のカラムの相関をみたい」など,各カラムの関係を描画できる.

df.plot.scatter(x='w', y='h')

整然データ(Tidy Data)

「整然データ」(Tidy Data)という概念は,データ分析をする上で有用な概念である.以下のリンクを見れば,「整然データ」という概念が良く理解できる.


その他

Jupyter での設定事項

Jupyter Notebook,Jupyter Lab の設定について.

Jupyter で DataFrame の表示を省略させない

Jupyter では DataFrame のカラムや行が表示しきれない場合は省略されて表示される.

省略させずに全てのカラム(または行)を表示させたい場合,それぞれ以下を実行することで,「全てのカラム(行)を表示」させたり,「特定のカラム数(行数)を表示」させたりと,表示の設定が行える.

  • カラムの表示数を調節
# 全てのカラムを表示する
pd.set_option('display.max_columns', None)

# カラムの表示数を設定する
num_columns = 3
pd.set_option('display.max_columns', num_columns)
  • 行の表示数を調節
# 全ての行を表示する
pd.set_option('display.max_rows', None)

# 行の表示数を設定する
num_rows = 5
pd.set_option('display.max_rows', num_rowss)

参考書籍

以下の書籍を参考にした.