LoginSignup
3
7

More than 3 years have passed since last update.

実務で使えそうなPandas関連操作の自分用メモ

Posted at

0. はじめに

最近、データサイエンス100本ノック(構造化データ加工編)を一通り解いてPandasの勉強をしました。
まずは答えを見ずにわからない箇所は自分で調べて全問解いた後、答え合わせをして勉強したのですが、今回の勉強を通じて、普段実務で使えそうだと思った処理を自分用にまとめました。

※あくまで自分用のまとめですので、出力結果や説明は割愛しており、以下の順番も100本ノックの順と無関係です。模範解答にはない処理もあります。

1. データセットの読み込み〜前処理

ファイル入出力

# ファイル読み込み。文字コードはUTF-8、Tab区切り
df = pd.read_csv('data/d.csv', header=0, encoding='utf-8', sep='\t')
# ファイル書き込み。ヘッダーあり、文字コードはUTF-8、カンマ区切り
df.to_csv('data/d.csv', header=True, encoding='utf-8', sep=',')
# ファイル書き込み。ヘッダーなし、文字コードは文字コードはCP932、Tab区切り
df.to_csv('data/d.csv', header=False, encoding='cp932', sep='\t') 

データフレームのコピー

df2 = df.copy()

キーを使ったデータフレームの結合

pd.merge(df_receipt, df_store[['store_cd', 'store_name']], on='store_cd', how='inner')
#inner:内部結合, left:左外部結合, right:右外部結合, outer:完全外部結合

データフレームの連結

pd.concat([df1, df2], axis=1)
#mergeと違い、リストで渡す。axis=0とすれば縦方向、axis=1とすれば横方向に結合される。

データフレームの件数(行数)

len(df)

ユニーク数

len(df['id'].unique())

ユニークな要素の値とその出現回数

df['id'].value_counts()

欠損値の処理

# 各列の欠損数の確認
df.isnull().sum()
# 欠損値が一つでも含まれるレコードを削除
df.dropna()
# fillnaは辞書型で一度に指定できる
df.fillna({'price': mean(df['price']),
           'cost': median(df['cost'])}) 

重複の削除

#subsetで対象とする列を設定して重複削除
df.drop_duplicates(subset=['name', 'cd'], keep='first', inplace=True)

列名の変更

# renameで任意の列名を変更
df.rename(columns={'ymd':'sales_date'}, inplace=True)
# リストで直接書き換える
df.columns = ['category', 'price', 'cost']

型キャスト

# 文字列型に変換
df['sales_date'].astype(str)
# True, Falseを1, 0に変換する
(df['sales']>2000).astype(int)

値の置換

code_dict = {
    'A': 11,
    'B': 22,
    'C': 33,
    'D': 44
}
df['name'].replace(code_dict) #一致しないときはそのまま
df['name'].map(code_dict) #一致しないときはNAN

条件を満たす行にアクセスし、値を代入

df.loc[df['age10']=='60代', 'age10'] = '60代以上'

条件を満たすか否かの0-1フラグを立てる

(df['sales'] != 0).apply(int) #salesが0でなければ1のフラグが立つ

列に関数を適用して変換

# 四捨五入する
df['price'].apply(lambda x: np.round(x))
# 1.1倍して、小数点以下を切り捨てる
df['price'].apply(lambda x: np.floor(x * 1.1))
# 常用対数化(底=10)する
df['sales'].apply(lambda x: math.log10(x))

複数の列に関数を適用して変換

# 2つの列の月の差を出す
df[['end_date', 'start_date']].apply(\
    lambda x: relativedelta(x[0], x[1]).years * 12 + relativedelta(x[0], x[1]).months, axis=1)
# 欠損値ならx[1](メジアン)。そうでなければそのまま
df[['price', 'median_price']].apply(\
    lambda x: np.round(x[1]) if np.isnan(x[0]) else x[0], axis=1)

値を標準化(平均0、標準偏差1)

# 1行で書く場合
df['sales_ss'] = preprocessing.scale(df['sales'])
from sklearn import preprocessing
scaler = preprocessing.StandardScaler()
scaler.fit(customer[['sales']])
customer['sales_ss'] = scaler.transform(customer[['sales']])

値を正規化(最小値0、最大値1)

from sklearn import preprocessing
scaler = preprocessing.MinMaxScaler()
scaler.fit(customer[['sales']])
customer['sales_mm'] = scaler.transform(customer[['sales']])

ダミー変数を作成

pd.get_dummies(df, columns=['cd'])
# columnsを指定すると特定の列だけ適用でき、object型でなくてもダミー化できる

文字列操作

# 先頭3文字の抽出
df['name'].str[0:3]
# 文字列の連結
df['gender'].str.cat((df['age10']).astype(str)) # gender列とage10列の文字列を結合する

数値条件による行の抽出

# queryメソッドによるデータ抽出。複数条件を指定するときにシンプルに書ける
df[['sales_date', 'id', 'cd', 'quantity', 'amount']]\
    .query('id == "XXXX" & (amount >= 1000 | quantity >=5)')
# (参考)queryメソッドを使わない場合、条件が複雑だと煩雑になる
target = (df['id']=="XXXX") & ((df['amount']>=1000) | (df['quantity']>=5))
df[target][['sales_date', 'id', 'cd', 'quantity', 'amount']]

文字列条件による行の抽出

# "SASS" から始まる行を抽出
df['store'].str.startswith('SASS')
# 1 で終わる行を抽出
df['id'].str.endswith('1')
# 札幌 を含む行を抽出
df['address'].str.contains('札幌')
# 正規表現を使った判定
df['cd'].str.contains('^[A-D]', regex=True) #A〜Dのいずれかから始まる
df['cd'].str.contains('[1-9]$', regex=True) #1〜9のいずれかで終わる
df['cd'].str.contains('^[A-D].*[1-9]$', regex=True) #A〜Dのいずれかから始まり、かつ、1〜9のいずれかで終わる
df['tel'].str.contains('^[0-9]{3}-[0-9]{3}-[0-9]{4}', regex=True) #電話番号が3桁-3桁-4桁

2. 集計

ソート

# sales列を基準に降順ソート
df.sort_values('sales', ascending=True).head(10) #Falseにすれば昇順
# 複数列を基準にソート。また各列それぞれ降順、昇順を指定できる
df.sort_values(['sales', 'id'], ascending=[False, True])
# sales が多い順(ascending=False)にランクを降る。method='min'とすると値が同じ時は同じ数字が振られる
df['sales'].rank(method='min', ascending=False)

groupbyによる集計

# 集計する列ごとに適用する集計関数を選ぶ
df.groupby('id', as_index=False).agg({'amount':'sum', 'quantity':'sum'}) 
# 1つの列に複数の集計を適用
df.groupby('id', as_index=False).agg({'ymd':['max', 'min']})
# 任意の関数(ここではpd.Series.mode)を指定
df.groupby('id', as_index=False).agg({'cd': pd.Series.mode})
# 無名関数lambdaを指定
df.groupby('id', as_index=False).agg({'amount': lambda x: np.var(x)})
# agg関数を使わない書き方も可能
df.groupby('id', as_index=False).max()[['id', 'ymd']]
df.groupby('id', as_index=False).sum()[['id', 'amount', 'quantity']]
# (参考)groupbyによる最頻値(モード)を集計する場合agg関数を使う
df.groupby('id', as_index=False).mode() #エラーが出る

クロス集計

pd.pivot_table(df, index='age10', columns='gender', values='amount', aggfunc='sum')
#index:表側, columns:表頭, values: 対象とする値, aggfunc: 集計方法)
pd.pivot_table(sales, index='id', columns='year', values='amount', aggfunc='sum', margins=True) 
#margins=True とすれば、総計・小計を出せる

4分位点の算出

df.quantile([0, 0.25, 0.5, 0.75, 1.0])[['amount']]

3. 日時関連の処理

時間変数の変換

# 文字列 → datetime型
pd.to_datetime(df['str_date'])
# エポック秒 → datetime型
pd.to_datetime(df['epoch'], unit='s')
# datetime型 → エポック秒
df['date'].astype(np.int64) // 10**9

※参考:https://stackoverflow.com/questions/15203623/convert-pandas-datetimeindex-to-unix-time

# datetime型 → 文字列 %Y:年4桁, %m:月2桁, %d:日2桁。 ※%大文字と小文字で意味が違うことに留意。たとえば%Mは分を意味する
df['date'].dt.strftime('%Y%m%d')

datitime変数から年・月・日の情報を取り出す

# 年情報を取り出す。monthで月、dayで日付を取り出せる
t.dt.year
# 0埋め2桁の文字列を得るにはstrftimeを使う
t.dt.strftime('%d')

時間変数の差分

# 日数差を出すときはdatetime型を単に引く
df['end_date'] - df['start_date']
# 月の差を出すときは、relativedeltaを使う
relativedelta(x0, x1).years * 12 + relativedelta(x0, x1).months

曜日の処理

# weekday関数で曜日を数字(月曜日からの日数)で出力
df['月曜からの経過日数'] = df['ymd'].apply(lambda x: x.weekday())
# その日の週の月曜日の日付を得る
df['当該週の月曜日'] = df['ymd'].apply(lambda x: x - relativedelta(days=x.weekday()))

時系列の処理

# 1時点前との差分
df['sales'].diff(1)
# 1時点前との変化率
df['sales'].pct_change(1)
# 1時点前の値
df['sales'].shift(1)
# 2時点前の値
df['sales'].shift(2)

4. サンプリング

ランダムサンプリング

# ランダムで10%のデータをサンプリング
df.sample(frac=0.1, random_state=5) 
# 性別(gender)の割合に基づきランダムに10%のデータを層化抽出
_, test = train_test_split(df, test_size=0.1, stratify=df['gender'], random_state=5)
# 学習データとテストデータに8:2の割合で分割する
df_train, df_test = train_test_split(df, test_size=0.2, random_state=5) 
# 1:1となるようにアンダーサンプリングする
r = RandomUnderSampler(random_state=5)
df_sample, _ = r.fit_sample(df, df['flag'])

参考

勉強するにあたって、以下を参考にさせていただきました。ありがとうございました。
- データサイエンス100本ノック(構造化データ加工編)
- note.nkmk.me

3
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
7