はじめに
- 自分は元々pandasが苦手でKaggleコンペ参加時は基本的にBigQuery上のSQLで特徴量を作り、最低限のpandas操作でデータ処理をしていました。
- しかし、あるコードコンペティションに参加することになり、pythonで軽快にデータ処理をこなす必要が出てきたので勉強しました。
- そこで、当時の勉強メモをもとに「これだけ知っていればKaggleでそこそこ戦えるかな」と思っているpandasの主要機能をまとめました。
注記
実戦入門
のつもりが ほぼ辞書
になってしまいました orz
- pandasとはなんぞや的な内容は書いていません
(import pandas
やDataFrameとは何かなど)
- pandas1.0系でも動くように書いたつもりですが間違ってたらすみません
目次
Options
jupyter notebook で DataFrame の表示が省略されないようにする。
なんだかんだ書き方をよく忘れる。
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
DaraFrame 読み書き
CSVファイル
読み込み
read_csv
は意外とオプションが多いのでなかなか覚えきれません。
df = pd.read_csv('train.csv')
df = pd.read_csv('train.csv', header=None)
df = pd.read_csv('train.csv', names=('col_1', 'col_2'))
df = pd.read_csv('train.csv', usecols=['col_1', 'col_3'])
df = pd.read_csv('train.csv', usecols=lambda x: x is not 'col_2')
df = df.rename(columns={'c': 'col_1'})
df = pd.read_csv('train.csv', dtype={'col_1': str, 'col_3': str})
df = df['col_1'].astype(int)
df = pd.read_csv('train.csv', parse_dates=['created_at', 'updated_at'])
書き出し
df.to_csv('file_name.csv')
submission.to_csv('submission.csv', index=False)
Pickleファイル
df = pd.read_pickle('df.pickle')
df.to_pickle('df.pickle')
df.to_pickle('df.pickle.zip')
df = pd.read_pickle('df.pickle.zip')
メモリ使用量削減の工夫
ファイルを読み込んだ直後にメモリ使用量削減するクセを付けておくと色々はかどります。
型変更
df = reduce_mem_usage(df)
df = df.read_csv('train.csv')\
.pipe(reduce_mem_usage)
df.pipe(h) \
.pipe(g, arg1=1) \
.pipe(f, arg2=2, arg3=3)
不要カラム削除
import gc
del df['col_1']; gc.collect();
データクリーニング
欠損データ処理
df1.dropna(how='any')
df = df[~df['col_1'].isnull()]
df1.fillna(value=0)
重複排除
df2.drop_duplicates()
df2.drop_duplicates(['col_1'])
df2.drop_duplicates(['col_1'], keep='last')
補間 (interpolate)
- Kaggleではあまり使わないかもだが、実務とかで役に立ちそう
DataFrame操作
DataFrame 情報表示
df.info()
df.shape
len(df)
df.head(5)
df.tail(5)
df.columns
df.describe()
df.describe(exclude='number')
df.describe(percentiles=[0.01, 0.25, 0.5, 0.75, 0.99])
Slice (iloc / loc / (ix))
df.iloc[3:5, 0:2]
df.loc[:, ['col_1', 'col_2']]
df.loc[df.index[[3, 4, 8]], ['col_3', 'col_5']]
型による列選択
df.select_dtypes(
include=['number', 'bool'],
exclude=['object'])
条件指定による行選択
df[df.age >= 25]
df[(df.age <= 19) | (df.age >= 30)]
df[(df.age >= 25) & (df.age <= 34)]
df[df['age'].between(25, 34)]
df[df.user_id.isin(target_user_list)]
df.query('age >= 25') \
.query('gender == "male"')
indexリセット
df = df.reset_index()
df.reset_index(inplace=True)
df.reset_index(drop=False, inplace=True)
列削除
df = df.drop(['col_1'], axis=1)
df = df.drop(['col_1'], axis=1, inplace=True)
Numpy Array 化
df['col_1'].values
連結・結合
連結
df = pd.concat([df_1, df_2, df_3])
df = pd.concat([df_1, df_2], axis=1)
df = pd.concat([df_1, df_2, df_3], join='inner')
結合
merge
: キーを指定しての結合
df = pd.merge(df, df_sub, on='key')
df = pd.merge(df, df_sub, on=['key_1', 'key_2'])
df = pd.merge(df, df_sub, on='key', how='left')
df = pd.merge(df, df_sub,
left_on='key_left', right_on='key_right') \
.drop('key_left', axis=1)
join
: indexを利用した結合
df_1.join(df_2)
df_1.join(df_2, how='inner')
ランダムサンプリング
df.sample(n=100)
df.sample(frac=0.25)
df.sample(frac=0.25, random_state=42)
df.sample(frac=0.25, replace=True)
df.sample(frac=0.25, axis=1)
ソート
df.sort_values(by='col_1')
df.sort_index(axis=1, ascending=False)
df.sort_values(by=['col_1', 'col_2'],
ascending=[False, True])
argmax / TOP-N 系の処理
df['col1'].idxmax()
df.sum().idxmin()
df.nlargest(5, ['col_1', 'col_2'])
各種演算
よく使う関数基礎
df['col_1'].sum()
df['col_1'].unique()
df['col_1'].nunique()
df['col_1'].quantile([0.25, 0.75])
df['col_1'].clip(-4, 6)
df['col_1'].clip(0, df['col_1'].quantile(0.99))
出現頻度カウント (value_counts)
df['col_1'].value_counts()
df['col_1'].value_counts(dropna=False)
df['col_1'].value_counts(normalize=True)
値の書き換え (apply / map)
Series各要素の書き換え: map
f_brackets = lambda x: '[{}]'.format(x)
df['col_1'].map(f_brackets)
df['priority'] = df['priority'].map({'yes': True, 'no': False})
DataFrameの各行・各列の書き換え: apply
df['col_1'].apply(lambda x: max(x))
df['col_1'].apply(lambda x: custom_func(x))
df['col_1'].progress_apply(lambda x: custom_func(x))
その他の書き換え (replace / np.where)
df['animal'] = df['animal'].replace('snake', 'python')
df['logic'] = np.where(df['AAA'] > 5, 'high', 'low')
condition_1 = (
(df.title == 'Bird Measurer (Assessment)') & \
(df.event_code == 4110)
)
condition_2 = (
(df.title != 'Bird Measurer (Assessment)') & \
(df.type == 'Assessment') & \
(df.event_code == 4100)
)
df['win_code'] = np.where(condition_1 | condition_2, 1, 0)
集約 (agg)
df.groupby(['key_id'])\
.agg({
'col_1': ['max', 'mean', 'sum', 'std', 'nunique'],
'col_2': [np.ptp, np.median]
})
df.groupby(['key_id_1', 'key_id_2'])\
.agg({
col: ['max', 'mean', 'sum', 'std']
for col in cols
})
集約結果の活用例
ほぼイディオムだが、最初は慣れないと処理に手間取るので例を書いておく。
agg_df = df.groupby(['key_id']) \
.agg({'col_1': ['max', 'min']})
agg_df.columns = [
'_'.join(col) for col in agg_df.columns.values]
agg_df.reset_index(inplace=True)
df = pd.merge(df, agg_df, on='key_id', how='left')
ピボットテーブルによる集計
pd.pivot_table(df, values=['D', 'E'], index=['A', 'C'],
aggfunc={'D': np.mean,
'E': [min, max, np.mean]})
ループを回さず配列同士の演算
列方向の平均値との差分を算出する時に便利です
df.sub(df.mean(axis=0), axis=1)
df.div(df.max(axis=0), axis=1)
ビン詰め (cut / qcut)
pd.cut(df['col_1'], 4)
pd.qcut(df['col_1'], 4)
時系列データでよく使う処理
shift
: 行・列方向に値をずらす
df.shift(periods=2)
df.shift(periods=-1)
df.shift(periods=2, axis='columns')
rolling
: 移動平均などの算出
df['col_1'].rolling(3).sum()
df['col_1'].rolling(3) \
.agg([sum, min, max, 'mean'])
cumsum
: 累積和
同様の関数に cummax
, cummin
もある
df.cumsum()
diff
, pct_change
: 行・列の差分・変化率を取得
df.diff()
df.diff(2)
df.diff(-1)
df.pct_change()
df.pct_change(freq='2D')
時間単位での集約
funcs = {'Mean': np.mean, 'Max': np.max}
df['col_1'].resample("5min").apply(funcs)
- pandasで時系列データをリサンプリングするresample, asfreq
- pandasの時系列データにおける頻度(引数freq)の指定方法
カテゴリ変数エンコーディング
カテゴリ変数エンコーディングの種類についてはこの資料が詳しい
One-Hot Encoding
tmp = pd.get_dummies(df['gender'], prefix='gender')
df = df.join(tmp).drop('gender', axis=1)
Label Encoding
from sklearn.preprocessing import LabelEncoder
cat_cols = ['category_col_1', 'category_col_2']
for col in cat_cols:
le = LabelEncoder().fit(list(
set(train[col].unique()).union(
set(test[col].unique()))
))
train[f'{col}'] = le.transform(train[col])
test[f'{col}'] = le.transform(test[col])
train = reduce_mem_usage(train)
test = reduce_mem_usage(test)
- 注記
- 上記方法だとtestにのみ含まれるラベルもencodingされてしまう
- 気持ち悪い場合は、trainにないものは一括で
-1
とかに書き換えてしまう
(個人的にはあまり気にしていないので正しいやり方かどうか不安…。)
- kaggle本実装
- kaggle本ではtrainに出てくるものだけでLabelEnconding
Frequency Encoding
for col in cat_cols:
freq_encoding = train[col].value_counts()
train[col] = train[col].map(freq_encoding)
test[col] = test[col].map(freq_encoding)
Target Encoding
target_encoding = df.groupby('col_1') \
.agg({'correct': ['mean', 'count']}) \
.reset_index() \
.query('count >= 1000') \
.rename(columns={
'correct': 'target_encoded_col_1',
}) \
.drop('count', axis=1)
train = pd.merge(
train, target_encoding, on='col_1', how='left')
test = pd.merge(
test, target_encoding, on='col_1', how='left')
- 上記の例は非常に雑な実装です。真面目にやるときはKaggle本の実装を読んでFoldごとに計算しましょう
文字列操作
pandas official method list にたくさん載っているので一度目を通すことをおすすめします。
基本
series.str.len()
series.str.replace(' ', '_')
series.str.starswith('m')
pattern = r'[0-9][a-z]'
series.str.contains(pattern)
クリーニング
series.str.lower()
series.str.capitalize()
series.str.extract('([a-zA-Z\s]+)', expand=False)
series.str.strip()
table = str.maketrans({
'、': ',',
'。': '.',
'・': '',
})
result = text.translate(table)
文字の変換にはstr.translate()が便利
日付系処理
基本
df['timestamp'] = pd.to_datetime(df['timestamp'])
dates = pd.date_range('20130101', periods=6)
pd.date_range('20120101', periods=100, freq='S')
df['20130102':'20130104']
df['timestamp'].astype('int64')
高度な日付抽出
- pandasにはとても複雑な日付抽出の仕組みが実装されており、
毎月の第4土曜日
や月初第一営業日
といった抽出も一瞬です。(日本の祝日が対応していないので後述のjpholiday
などで多少変更は必要ですが。)
- pandasの時系列データにおける頻度(引数freq)の指定方法 に詳しいので、日付関係の実装が必要な際はぜひ一読されることをおすすめします。
pd.date_range('2020-01-01', '2020-12-31', freq='M')
pd.date_range('2020-01-01', '2020-12-31', freq='WOM-4SAT')
祝日判定
import jpholiday
import datetime
jpholiday.is_holiday(datetime.date(2017, 1, 1))
jpholiday.is_holiday(datetime.date(2017, 1, 3))
jpholiday.month_holidays(2017, 5)
可視化
デザインを綺麗にするおまじない
このQiita記事に載っているおまじないを書いておくと、グラフがとても綺麗になるのでとてもおすすめです。
import matplotlib
import matplotlib.pyplot as plt
plt.style.use('ggplot')
font = {'family' : 'meiryo'}
matplotlib.rc('font', **font)
シンプルなグラフ
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
df['col_1'].plot()
df.plot(subplots=True, layout=(2, 2))
df.plot(subplots=True, layout=(2, 2),
sharex=True, sharey=True)
ヒストグラム
df['col_1'].plot.hist()
df['col_1'].plot.hist(bins=20, rwidth=.8)
df['col_1'].plot.hist(bins=range(0, 101, 5), rwidth=.8)
df['col_1'].plot.hist(alpha=0.5)
df['col_1'].plot.hist(ylim=(0, 0.25))
箱ひげ図
df['col_1'].plot.box()
分布図
df.plot.scatter(x='col_1', y='col_2')
並列処理
- pandasでの処理は残念ながら速くはないと思います。BigQuery等と比較すると残念なレベルです。(まぁ処理の速さそのものを比較するのはアンフェアですが…。)
- 大量の特徴量を全て正規化するときや、大量の要素にmapをかける時とかは並列処理を駆使すると便利だと思います。
from multiprocessing import Pool, cpu_count
def parallelize_dataframe(df, func, columnwise=False):
num_partitions = cpu_count()
num_cores = cpu_count()
pool = Pool(num_cores)
if columnwise:
df_split = [df[col_name] for col_name in df.columns]
df = pd.concat(pool.map(func, df_split), axis=1)
else:
df_split = np.array_split(df, num_partitions)
df = pd.concat(pool.map(func, df_split))
pool.close()
pool.join()
return df
df = parallelize_dataframe(df, custom_func, columnwise=True)
'20/07/28 追記
- pandaparallelやswifterという並行処理を行ってくれるライブラリが充実してきているようです。
おまけ: Excel読み書き
kaggleでは使わないけど実務で使う人一定数いる? (僕は使ったことない)
df.to_excel('foo.xlsx', sheet_name='Sheet1')
pd.read_excel('foo.xlsx', 'Sheet1', index_col=None, na_values=['NA'])
pandasを身につけるには?
まずは、おとなしく公式Tutorialに載ってるようなmaterialを以下のような順番で一通り回るのが最速かと思います。(可視化以外)
- 10 Minutes to pandas
- Pandas cookbook
- Cheet Sheet
- Cookbook
実践的な問題をやりたいときは前処理大全をやるのも良いかもですが、Kaggleコンペに参加する場合は公開Notebookを見ながら練習する程度でも十分かと思います。
リンク
おわりに
Kaggle関係の色々な記事を書いているので、良かったら読んでみてください〜。
実践的なTips集
naotaka1128.hatenadiary.jp
コンペ参戦記
naotaka1128.hatenadiary.jp
naotaka1128.hatenadiary.jp
naotaka1128.hatenadiary.jp
naotaka1128.hatenadiary.jp