実戦入門
のつもりがほぼ辞書
になってしまいました orzimport pandas
やDataFrameとは何かなど)jupyter notebook で DataFrame の表示が省略されないようにする。なんだかんだ書き方をよく忘れる。
pd.set_option('display.max_columns',None)pd.set_option('display.max_rows',None)
read_csv
は意外とオプションが多いのでなかなか覚えきれません。
# 基本df = pd.read_csv('train.csv')# headerがないとき (列名は連番になる)df = pd.read_csv('train.csv', header=None)# headerがなくて自分で列名指定したいときdf = pd.read_csv('train.csv', names=('col_1','col_2'))# 利用する列を指定したいときdf = pd.read_csv('train.csv', usecols=['col_1','col_3'])# lamda式も利用可能df = pd.read_csv('train.csv', usecols=lambda x: xisnot'col_2')# 列名: 読み込んだあとの変更df = df.rename(columns={'c':'col_1'})# 型指定で読み込み (指定した列以外は自動推定)## メモリ逼迫しているとき以外は、型指定せず read_csv して、## 後述の `reduce_mem_usage` を使うことも多いdf = pd.read_csv('train.csv', dtype={'col_1':str,'col_3':str})## 型: 読み込んだあとの変更df = df['col_1'].astype(int)# float / str / np.int8 ...# 時間系データをparsedf = pd.read_csv('train.csv', parse_dates=['created_at','updated_at'])
# 基本df.to_csv('file_name.csv')# index不要のとき (kaggle submission fileは不要なので忘れがち)submission.to_csv('submission.csv', index=False)
# 基本df = pd.read_pickle('df.pickle')df.to_pickle('df.pickle')# データが重いときはzip化できる (が遅くて実用に耐えないらしい)## 書き出し: 拡張子を zip や gzip にするだけでよいdf.to_pickle('df.pickle.zip')## 読み込み: read_pickle は拡張子を見て自動的に解凍処理をしてくれるdf = pd.read_pickle('df.pickle.zip')
ファイルを読み込んだ直後にメモリ使用量削減するクセを付けておくと色々はかどります。
# kaggleでよく使われる `reduce_mem_usage` でメモリ使用量削減## 内部では各カラムの値域に合わせて型変更を行っている## `reduce_mem_usage` 実装は ref 参照df = reduce_mem_usage(df)# 実践的には read_csv した直後にメモリ使用量削減を行うことも多いdf = df.read_csv('train.csv')\ .pipe(reduce_mem_usage)# 余談だが、pipeを使うと可読性向上することが多い# f(g(h(df), arg1=1), arg2=2, arg3=3)df.pipe(h)\ .pipe(g, arg1=1)\ .pipe(f, arg2=2, arg3=3)
import gc# dropでも良い: df.drop('col_1', axis=1, inplace=True)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')# keep='first' / False(drop all)
# 行数,列数,メモリ使用量,データ型,非欠損要素数の表示df.info()# 行数 x 列数 取得df.shape# 行数取得len(df)# 最初 / 最後のN行表示df.head(5)df.tail(5)# カラム名一覧を取得df.columns# 各要素の要約統計量を取得## 数値型要素の min/max/mean/stdなどを取得df.describe()## カテゴリ型要素の count/unique/freq/stdなどを取得df.describe(exclude='number')## 表示するパーセンタイルを指定df.describe(percentiles=[0.01,0.25,0.5,0.75,0.99])
# 基本df.iloc[3:5,0:2]df.loc[:, ['col_1','col_2']]# 行は数値で指定して、列は名前で指定する# (バージョンによっては ix でもできるが廃止された)df.loc[df.index[[3,4,8]], ['col_3','col_5']]
# 除外もできるdf.select_dtypes( include=['number','bool'], exclude=['object'])
# 基本df[df.age >=25]# OR条件df[(df.age <=19) | (df.age >=30)]# AND条件df[(df.age >=25) & (df.age <=34)]## betweenでも書ける (あまり見ないが)df[df['age'].between(25,34)]# INdf[df.user_id.isin(target_user_list)]# query記法: 賛否両論あるが個人的には好きdf.query('age >= 25')\ .query('gender == "male"')
# 基本df = df.reset_index()# 破壊的変更df.reset_index(inplace=True)# drop=Falseにするとindexが列として追加されるdf.reset_index(drop=False, inplace=True)
# 基本df = df.drop(['col_1'], axis=1)# 破壊的変更df = df.drop(['col_1'], axis=1, inplace=True)
# df['col_1'] のままだと index が付いてきて# 他のdfにくっつけるときにバグを引き落とすようなこともあるので# numpy array にして後続の処理を行うことも多々あるdf['col_1'].values
# concat## 基本 (縦に積む: カラムは各DataFrameの和集合df = pd.concat([df_1, df_2, df_3])## 横につなげるdf = pd.concat([df_1, df_2], axis=1)## 各DataFrameに共通のカラムのみで積む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を利用した結合# 基本 (左結合: mergeと違うので注意)df_1.join(df_2)# 内部結合df_1.join(df_2, how='inner')
# 100行抽出df.sample(n=100)# 25%抽出df.sample(frac=0.25)# seed固定df.sample(frac=0.25, random_state=42)# 重複許可: デフォルトはreplace=Falsedf.sample(frac=0.25, replace=True)# 列をサンプリングdf.sample(frac=0.25, axis=1)
# 基本df.sort_values(by='col_1')# indexでソートdf.sort_index(axis=1, ascending=False)# キーを複数 & 降昇順指定df.sort_values(by=['col_1','col_2'], ascending=[False,True])
# 最も値が小さな行/列を見つけるdf['col1'].idxmax()# 最も和が小さな列を見つけるdf.sum().idxmin()# TOP-N: col_1で上位5件を出す → 同一順位であればcol_2を見るdf.nlargest(5, ['col_1','col_2'])# .smallest: 下位N件
# 集計df['col_1'].sum()# mean / max / min / count / ...# ユニーク値取得df['col_1'].unique()# ユニーク要素個数 (count distinct)df['col_1'].nunique()# percentiledf['col_1'].quantile([0.25,0.75])# clippingdf['col_1'].clip(-4,6)# 99パーセンタイルでclippingdf['col_1'].clip(0, df['col_1'].quantile(0.99))
# (NaN除く)df['col_1'].value_counts()# 出現頻度カウント(NaN含む)df['col_1'].value_counts(dropna=False)# 出現頻度カウント (合計を1に正規化)df['col_1'].value_counts(normalize=True)
map
# 各要素に特定の処理f_brackets =lambda x:'[{}]'.format(x)df['col_1'].map(f_brackets)# 0 [11]# 1 [21]# 2 [31]# Name: col_1, dtype: object# dictを渡して値の置換df['priority'] = df['priority'].map({'yes':True,'no':False})
apply
# 基本df['col_1'].apply(lambda x:max(x))# もちろん自身で定義した関数でも良いdf['col_1'].apply(lambda x: custom_func(x))# 進捗を表示するときは# from tqdm._tqdm_notebook import tqdm_notebookdf['col_1'].progress_apply(lambda x: custom_func(x))
# replacedf['animal'] = df['animal'].replace('snake','python')# np.wheredf['logic'] = np.where(df['AAA'] >5,'high','low')# np.where: 複雑ver.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)
# 基本df.groupby(['key_id'])\ .agg({'col_1': ['max','mean','sum','std','nunique'],'col_2': [np.ptp, np.median]# np.ptp: max - min })# 全ての列を一律で集約したいときはリスト内包表記で書いてしまっても良いdf.groupby(['key_id_1','key_id_2'])\ .agg({ col: ['max','mean','sum','std']for colin cols })
ほぼイディオムだが、最初は慣れないと処理に手間取るので例を書いておく。
# 集約agg_df = df.groupby(['key_id'])\ .agg({'col_1': ['max','min']})# カラム名が max / min になり、どのキーのものか区別できないので修正する# マルチインデックスになっているのでバラして rename するagg_df.columns = ['_'.join(col)for colin agg_df.columns.values]# 集約結果はindexにkey_idが入っているのでreset_indexで出すagg_df.reset_index(inplace=True)# key_idをキーとして元のDataFrameと結合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]})# D E# mean max mean min# A C# bar large 5.500000 9.0 7.500000 6.0# small 5.500000 9.0 8.500000 8.0# foo large 2.000000 5.0 4.500000 4.0# small 2.333333 6.0 4.333333 2.0
列方向の平均値との差分を算出する時に便利です
# `df['{col}_diff_to_col_mean] = df['{col}'] - df['{col}'].mean()` 的な処理を一括でやる時df.sub(df.mean(axis=0), axis=1)# sub 以外にも add / div / mul (掛け算) もある# 以下は `df['{col}_div_by_col_max] = df['{col}'] / df['{col}'].max()` の一括処理df.div(df.max(axis=0), axis=1)
# df['col_1']の最小値と最大値の間を4分割 → その境界を使ってビン詰め# つまり、各ビンに含まれる個数がバラけるpd.cut(df['col_1'],4)# df['col_1']の要素数を4等分してビンを作る → その後に境界を求める# つまり、ビンの間隔がバラけるpd.qcut(df['col_1'],4)
shift
: 行・列方向に値をずらす# 2行下にずらすdf.shift(periods=2)# 1行上にずらすdf.shift(periods=-1)# 2列ずらす (あまり使わない)df.shift(periods=2, axis='columns')
rolling
: 移動平均などの算出# window幅=3の窓関数により合計値を算出df['col_1'].rolling(3).sum()# 複数のdf['col_1'].rolling(3)\ .agg([sum,min,max,'mean'])
cumsum
: 累積和同様の関数にcummax
,cummin
もある
# df# A B# 0 2.0 1.0# 1 3.0 NaN# 2 1.0 0.0# 上記のdfの累計和を算出df.cumsum()# A B# 0 2.0 1.0# 1 5.0 NaN# 2 6.0 1.0
diff
,pct_change
: 行・列の差分・変化率を取得# 例で使うdataframe# col_1 col_2# 0 1 2# 1 2 4# 2 3 8# 3 4 16# 基本: 1行前との差分を算出df.diff()# col_1 col_2# 0 NaN NaN# 1 1.0 2.0# 2 1.0 4.0# 3 1.0 8.0# 2行前との差分算出df.diff(2)# col_1 col_2# 0 NaN NaN# 1 NaN NaN# 2 2.0 6.0# 3 2.0 12.0# 負の数も指定可能df.diff(-1)# col_1 col_2# 0 -1.0 -2.0# 1 -1.0 -4.0# 2 -1.0 -8.0# 3 NaN NaN# 変化率を取得するときは `pct_change`df.pct_change()# col_1 col_2# 0 NaN NaN# 1 1.000000 1.0# 2 0.500000 1.0# 3 0.333333 1.0# 計算対象がdatetimeの場合は頻度コードで指定可能# 以下の例では `2日前` のデータとの変化率を算出df.pct_change(freq='2D')
# 5分おきに平均、最大値を集計# 頻度コード `min` `H` などの詳細は ref.2 に非常に詳しいので参照のことfuncs = {'Mean': np.mean,'Max': np.max}df['col_1'].resample("5min").apply(funcs)
カテゴリ変数エンコーディングの種類についてはこの資料が詳しい
# この DataFrame を処理する# name gender# 0 hoge male# 1 fuga NaN# 2 hage female# prefixを付けることでなんのカラムのOne-Hotかわかりやすくなるtmp = pd.get_dummies(df['gender'], prefix='gender')# gender_female gender_male# 0 0 1# 1 0 0# 2 1 0# 結合したあと元のカラムを削除するdf = df.join(tmp).drop('gender', axis=1)# name gender_female gender_male# 0 hoge 0 1# 1 fuga 0 0# 2 hage 1 0
from sklearn.preprocessingimport LabelEncoder# trainとtestに分かれているデータを一括でLabelEncodingする例cat_cols = ['category_col_1','category_col_2']for colin cat_cols:# 慣例的に `le` と略すことが多い気がする le = LabelEncoder().fit(list(# train & test のラベルの和集合を取るset(train[col].unique()).union(set(test[col].unique())) )) train[f'{col}'] = le.transform(train[col]) test[f'{col}'] = le.transform(test[col])# label encoding したらメモリ使用量も減らせるので忘れずにtrain = reduce_mem_usage(train)test = reduce_mem_usage(test)
-1
とかに書き換えてしまう(個人的にはあまり気にしていないので正しいやり方かどうか不安…。)for colin cat_cols: freq_encoding = train[col].value_counts()# ラベルの出現回数で置換 train[col] = train[col].map(freq_encoding) test[col] = test[col].map(freq_encoding)
# 超雑にやるとき (非推奨)## col_1の各ラベルに対して target(correct) の平均値とカウントを算出## 一定のカウント未満(仮に1000件)のラベルは無視して集計する、という例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')
pandas official method list にたくさん載っているので一度目を通すことをおすすめします。
# 文字数series.str.len()# 置換series.str.replace(' ','_')# 'm' から始まる(終わる)かどうかseries.str.starswith('m')# endswith# 表現を含んでいるかどうかpattern =r'[0-9][a-z]'series.str.contains(pattern)
# 大文字/小文字series.str.lower()# .upper()# capitalize (male → Male)series.str.capitalize()# 英数字抽出: 最初の適合部分だけだけ## マッチが複数の場合はDFが返ってくる## extractall: すべての適合部分がマルチインデックスで返ってくるseries.str.extract('([a-zA-Z\s]+)', expand=False)# 前後の空白削除series.str.strip()# 文字の変換## 変換前: Qiitaは、プログラミングに関する知識を記録・共有するためのサービスです。## 変換後: Qiitaは,プログラミングに関する知識を記録共有するためのサービスです.table =str.maketrans({'、':',','。':'.','・':'',})result = text.translate(table)
# 基本: 読み込み時に変換忘れたときとかdf['timestamp'] = pd.to_datetime(df['timestamp'])# 日付のリストを作成dates = pd.date_range('20130101', periods=6)# 日付のリストを作成: 秒単位で100個pd.date_range('20120101', periods=100, freq='S')# 日付でフィルタdf['20130102':'20130104']# unixtime にするdf['timestamp'].astype('int64')
毎月の第4土曜日
や月初第一営業日
といった抽出も一瞬です。(日本の祝日が対応していないので後述のjpholiday
などで多少変更は必要ですが。)# 月の最終日を抽出するpd.date_range('2020-01-01','2020-12-31', freq='M')# DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31', '2020-04-30',# '2020-05-31', '2020-06-30', '2020-07-31', '2020-08-31',# '2020-09-30', '2020-10-31', '2020-11-30', '2020-12-31'],# dtype='datetime64[ns]', freq='M')# 2020年の第4土曜日を抽出するpd.date_range('2020-01-01','2020-12-31', freq='WOM-4SAT')# DatetimeIndex(['2020-01-25', '2020-02-22', '2020-03-28', '2020-04-25',# '2020-05-23', '2020-06-27', '2020-07-25', '2020-08-22',# '2020-09-26', '2020-10-24', '2020-11-28', '2020-12-26'],# dtype='datetime64[ns]', freq='WOM-4SAT')
import jpholidayimport datetime# 指定日が祝日か判定jpholiday.is_holiday(datetime.date(2017,1,1))# Truejpholiday.is_holiday(datetime.date(2017,1,3))# False# 指定月の祝日を取得jpholiday.month_holidays(2017,5)# [(datetime.date(2017, 5, 3), '憲法記念日'),# (datetime.date(2017, 5, 4), 'みどりの日'),# (datetime.date(2017, 5, 5), 'こどもの日')]
このQiita記事に載っているおまじないを書いておくと、グラフがとても綺麗になるのでとてもおすすめです。
import matplotlibimport matplotlib.pyplotas pltplt.style.use('ggplot') font = {'family' :'meiryo'}matplotlib.rc('font', **font)
import pandasas pdimport matplotlibas mplimport matplotlib.pyplotas plt# 基本df['col_1'].plot()# 複数のカラムのプロットを 2x2 のタイル状に表示# (カラム数がタイル数を超えていると怒られる)df.plot(subplots=True, layout=(2,2))# 上記でX軸,Y軸の共通化df.plot(subplots=True, layout=(2,2), sharex=True, sharey=True)
# ヒストグラムdf['col_1'].plot.hist()# binを20に増やす / バーの幅を細くして間を開けるdf['col_1'].plot.hist(bins=20, rwidth=.8)# X軸のレンジを指定## 0-100歳を5歳刻みで表示するイメージdf['col_1'].plot.hist(bins=range(0,101,5), rwidth=.8)# ヒストグラムが重なる時に透過させるdf['col_1'].plot.hist(alpha=0.5)# Y軸の最小値・最大値を固定df['col_1'].plot.hist(ylim=(0,0.25))
df['col_1'].plot.box()
df.plot.scatter(x='col_1', y='col_2')
from multiprocessingimport Pool, cpu_countdefparallelize_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_namein 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# 適当な関数にDataFrameを突っ込んで列方向に並列処理するdf = parallelize_dataframe(df, custom_func, columnwise=True)
kaggleでは使わないけど実務で使う人一定数いる? (僕は使ったことない)
# writedf.to_excel('foo.xlsx', sheet_name='Sheet1')# readpd.read_excel('foo.xlsx','Sheet1', index_col=None, na_values=['NA'])
まずは、おとなしく公式Tutorialに載ってるようなmaterialを以下のような順番で一通り回るのが最速かと思います。(可視化以外)
実践的な問題をやりたいときは前処理大全をやるのも良いかもですが、Kaggleコンペに参加する場合は公開Notebookを見ながら練習する程度でも十分かと思います。
Kaggle関係の色々な記事を書いているので、良かったら読んでみてください〜。
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。