Python:Pandas チートシート
- 各種参考リンク
- 表形式のデータの構成要素
- Pandas の主要なデータ型:pd.Series と pd.DataFrame
- Pandas を用いてデータを作る
- pd.DataFrame の基本的な操作
- 特定の行,列,行列を抽出する
- 要素・行・列に関数を適用する:map,applymap,apply
- インデックスの操作
- 欠損値(NaN)の処理
- データの要約
- データの整形
- テーブルの結合:merge,join,concat
- 階層インデックス MultiIndex の使い方
- [TODO]データ分析の tips
- [TODO]時系列データの処理
- [TODO]メソッドチェーン
- [TODO]window 関数
- プロット(描画)
- 整然データ(Tidy Data)
- その他
- 参考書籍
最近,Python のデータ処理のためのライブラリ Pandas についてキャッチアップしているので,その備忘録として自分用チートシートを作ってみた.項目については ↑ の目次を見てください.
各種参考リンク
本記事は以下のリンクを参考に自分なりにまとめている(恐らく Pandas の大体は網羅できる.当然筆者は全部見れてない).
- Pandas 公式ドキュメント
- Pandas 公式チートシート(日本語訳)
- note.nkmk.me | Pandas 関連記事まとめ
- 日本語の Python 記事の神
- ゆるふわ Pandas チートシート(Qiita, 2018)
- DeepAge | 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,JSON,Excel ファイルなど,様々な形式のファイルを pd.DataFrame
として読み込むことができる.
使うのは pd.read_**
(**
には特定のファイル形式が入る)という関数.
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.index
,DataFrame.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
- 公式ドキュメント
- note.nkmk.me:pandas.DataFrameの行を条件で抽出するquery
- query のパフォーマンスについて
クエリの評価エンジン
df.query()
の引数,engine
で評価エンジンを指定できる
基本
シングル / ダブルクォーテーションの使い分けに注意.
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
インデックスの操作
インデックスの操作を取り上げる
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)
注意点:pivot_table 実行後にインデックスが MultiIndex になる
pivot_table の引数 index
,columns
に渡す引数としてリスト型を渡すと必ず 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
列データの前処理のテクニック
整数型のカラムの 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 で表す:国数英理社 ->
00110
,11100
- ex1) 好きな科目を 1 で表す:国数英理社 ->
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)
x
,y
はプロットしたいカラム名を指定.「特定のカラムの相関をみたい」など,各カラムの関係を描画できる.
df.plot.scatter(x='w', y='h')
整然データ(Tidy Data)
「整然データ」(Tidy Data)という概念は,データ分析をする上で有用な概念である.以下のリンクを見れば,「整然データ」という概念が良く理解できる.
- Colorless Green Ideas | 整然データとは何か
- 「整然データ」という概念を非常に分かりやすく解説している
- Colorless Green Ideas | 【翻訳】整然データ
- 「整然データ」という概念を提唱した論文 "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)
参考書籍
以下の書籍を参考にした.