Pandasのアクセサ、特にstrアクセサについては、シンタックスの外面がやや特殊に感じておおりました。
よって、挙動になれるまではどこまでOKなのかわかりにくいので、気持ちのどこかで利用をさけていたのですが、ある時期以降は宣言的にロジックを書けるので、やっぱり結構便利だなぁと思うところがあります。
ということで、strアクセサだけではないのですが、その類の「Pandas触り始めは避けてたけど、(雰囲気で使っているなど)慣れてくるとそれなりに使うアクセサ」などで、最近気になったものを雑多に集めてみました。
- 1. Series.strアクセサ
- str.contains
- 2. dtアクセサ
- 3. Series.idxmax
- 4. 少し脱線してnumpyから少し
- 5. DataFrameどおしの演算
- 6. DataFrame.combine_first
- 7. transform
※例によって体系的・網羅的ではなくて、気まぐれマイチョイスです。
なお、以下の例は、すべて事前に次のコードを走行した後の例としています。
import pandas as pd import io #StringIOを使うため pesedo_csv = """ A,B,C,D,F,G a,b,c,X1:Y:Z,2,10 d,e,f,X2:Y:Z,9,20 g,a,b,X3:Y:Z,1,30 """ df = pd.read_csv(io.StringIO(pesedo_csv))
1. Series.strアクセサ
strアクセサは思った以上にいろいろできますが、特に良いなと思ったのは次のものです。
str.split(",").str.get(0)
df['D'].str.split(":").str.get(0)
説明しづらいので実際の例で。
In [86]: df Out[86]: A B C D F G 0 a b c X1:Y:Z 2 10 1 a b c X2:Y:Z 9 20 2 a b c X3:Y:Z 1 30 In [87]: df["D"].str.split(",").str.get(0) Out[87]: 0 X1:Y:Z 1 X2:Y:Z 2 X3:Y:Z Name: D, dtype: object
知らずに同じことをしようとすると、最初簡単だと思ったのに思ったより手間となりがちな例。
df['D'].str.split(":").str[0] も可。
ちょっと正確な言い方ではありませんが、ベクトル化された関数みたいなシンタックスでやりたいことがダイレクトにできますね。
以降、タイプ量低減のため、df['D']などの書き方については、「df.D」の表記としているものがあります。
str.split(separator,expand=True)
df.D.str.split(":",expand=True)
In [100]: df Out[100]: A B C D F G 0 a b c X1:Y:Z 2 10 1 a b c X2:Y:Z 9 20 2 a b c X3:Y:Z 1 30 In [101]: df.D.str.split(":",expand=True) Out[101]: 0 1 2 0 X1 Y Z 1 X2 Y Z 2 X3 Y Z
やや基本に戻って str[要素番号の指定]
df.D.str[0:2]
In [88]: df.D.str[0:2] Out[88]: 0 X1 1 X2 2 X3 Name: D, dtype: object
str.extract
str.splitより複雑な分割もできます。 (説明は略)
pandas.Series.str.extract — pandas 1.0.3 documentation
str.replace
名前のとおり置換
df.A.str.replace('a','aaa')
※正規表現とかも適用できたはず。
In [66]: df.D.str.replace('X','XXX') Out[66]: 0 XXX1:Y:Z 1 XXX2:Y:Z 2 XXX3:Y:Z Name: D, dtype: object
コラム: DataFrame.replace
DataFrameのreplaceもあります。該当する場合、全ての要素に適用されます。
df.replace('a','AAA')
デフォルトは完全一致なので注意。注意というかDataFrame全体に適用されることを考えると、自然な考え方だと思いますが。
str.contains
Booleanインデックスと組み合わせて力を発揮。
詳しい説明は割愛するが、DataFrame.queryでは難しい条件もできるかもしれない。
2. dtアクセサ
Datetime propertiesです。
時系列データをよく使う人ととそうでない人に分かれると思いますが、普段利用しない人は暗記は不要なものの、存在を知らないといざという時に損するかもしれません。
yearとかmonthはもちろん便利ですが、weekとかis_month_end(月終わりの日か)のようなis_XXXや曜日を判定するものはグループ化などその先のややこしい処理のハードルを下げてくれそうです。
In [81]: timetable = pd.DataFrame({ ...: 'time': pd.to_datetime(['20160525 13:30:00.023', ...: '20160526 13:30:00.023', ...: '20160527 13:30:00.030', ...: '20160528 13:30:00.041', ...: '20160529 13:30:00.048', ...: '20160531 13:30:00.049', ...: '20170525 13:30:00.072', ...: '20180625 13:30:00.075'])}, ...: columns=['time']) In [82]: timetable.time.dt.year Out[82]: 0 2016 1 2016 2 2016 3 2016 4 2016 5 2016 6 2017 7 2018 Name: time, dtype: int64 In [83]: timetable.time.dt.week Out[83]: 0 21 1 21 2 21 3 21 4 21 5 22 6 21 7 26 Name: time, dtype: int64 In [85]: timetable.time.dt.is_month_end Out[85]: 0 False 1 False 2 False 3 False 4 False 5 True 6 False 7 False Name: time, dtype: bool
3. Series.idxmax
値が最大となる行番号を戻す。
In [93]: df Out[93]: A B C D F G 0 a b c X1:Y:Z 2 10 1 a b c X2:Y:Z 9 20 2 a b c X3:Y:Z 1 30 In [94]: df.F.idxmax() Out[94]: 1 Fの値が9で「最大である行」のインデックス「1」が戻る。
よって、次のような使い方が可能。 i = df.F.idxmax() df.iloc[i]
また、ここでは詳しく述べないが、Series.applyなどで、最大値との比率などを計算する際にも使える。
4. 少し脱線してnumpyから少し
目的外使用かもしれないが、numpyは試験データ(DataFrame初期化)の半自動生成などに便利。
全て覚えておかなくても、その都度調べる価値ありだが、以下は覚えておいてよいかも。
import numpy as np
np.zeros(50)
np.ones(50)
np.empty(50)
5. DataFrameどおしの演算
DataFrame.gt
df1.gt(df2)
df1とdf2の対応する要素の比較
和(?)
In [95]: df + df Out[95]: A B C D F G 0 aa bb cc X1:Y:ZX1:Y:Z 4 20 1 aa bb cc X2:Y:ZX2:Y:Z 18 40 2 aa bb cc X3:Y:ZX3:Y:Z 2 60 In [96]:
文字列でもくっつけちゃう。
スカラー値をかける
df * 2
これはブロードキャストとしては自然かな。
同一かの比較
df + df == df * 2
In [96]: df + df == df * 2 Out[96]: A B C D F G 0 True True True True True True 1 True True True True True True 2 True True True True True True In [97]:
↓ dfと同じ構造のTrue or Falseの DataFrameが戻る。 ビジネスロジックの実装時にはあまり使わないかもしれないが、データラングリングの途中でassertionに使うといったことはありそう。 あるいは全体的に「同じ」とか「違う」という場合のやや揺らぎを考慮した比較とかはありかも。
6. DataFrame.combine_first
df1.combine_first(df2)
下記で手厚い説明をされているのでそちらを参照。
値が取れない場合にデフォルト値を設定したいが、fillnaだと大まかすぎるので、初期化用のDataFrame(上記のdf2)を用意しておくというような用にに便利だとおもいました。
7. transform
ひとつの列から複数の演算の結果の値(DataFrame)を派生
df.transform([np.abs, lambda x: x + 1, np.abs])
↑一見意味不明かもですが、transformで何ができるかみてみてください。
In [97]: df Out[97]: A B C D F G 0 a b c X1:Y:Z 2 10 1 a b c X2:Y:Z 9 20 2 a b c X3:Y:Z 1 30 In [98]: df.transform([np.abs, lambda x: x + 1, np.abs]) Out[98]: F G absolute <lambda> absolute absolute <lambda> absolute 0 2 3 2 10 11 10 1 9 10 9 20 21 20 2 1 2 1 30 31 30
transformは、こんな↓感じのこともできる。 df.transform({'F': np.abs, 'G': [lambda x: x + 1, 'sqrt']})