今回はDataFrameに新しいカラムを追加する方法をまとめました。
例えば全て1という値のカラムを追加したり、特定のルールにしたがって区分値を付与し直す時などに新規カラムを追加することになります。
import pandas as pd
df = pd.read_csv("http://lib.stat.cmu.edu/datasets/boston_corrected.txt", encoding='Windows-1252',skiprows=9,sep="\t")
df.head()
OBS. TOWN TOWN# TRACT LON LAT MEDV CMEDV CRIM ZN ... CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT 0 1 Nahant 0 2011 -70.955 42.2550 24.0 24.0 0.00632 18.0 ... 0 0.538 6.575 65.2 4.0900 1 296 15.3 396.90 4.98 1 2 Swampscott 1 2021 -70.950 42.2875 21.6 21.6 0.02731 0.0 ... 0 0.469 6.421 78.9 4.9671 2 242 17.8 396.90 9.14 2 3 Swampscott 1 2022 -70.936 42.2830 34.7 34.7 0.02729 0.0 ... 0 0.469 7.185 61.1 4.9671 2 242 17.8 392.83 4.03 3 4 Marblehead 2 2031 -70.928 42.2930 33.4 33.4 0.03237 0.0 ... 0 0.458 6.998 45.8 6.0622 3 222 18.7 394.63 2.94 4 5 Marblehead 2 2032 -70.922 42.2980 36.2 36.2 0.06905 0.0 ... 0 0.458 7.147 54.2 6.0622 3 222 18.7 396.90 5.33 5 rows × 21 columns
df.columns
Index(['OBS.', 'TOWN', 'TOWN#', 'TRACT', 'LON', 'LAT', 'MEDV', 'CMEDV', 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT'], dtype='object')
① 固定値を代入した列の追加
値が1の列を追加
df["FLG"] = 1
df.columns
Index(['OBS.', 'TOWN', 'TOWN#', 'TRACT', 'LON', 'LAT', 'MEDV', 'CMEDV', 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'FLG'], dtype='object')
FLGカラムが追加されたようです。
df.loc[:,["FLG"]]
FLG 0 1 1 1 2 1 3 1 4 1 ... ... 501 1 502 1 503 1 504 1 505 1 506 rows × 1 columns
df.loc[:,["FLG"]].quantile([0,0.1,0.25,0.5,0.75,0.9,0.95,0.99,1])
FLG 0.00 1.0 0.10 1.0 0.25 1.0 0.50 1.0 0.75 1.0 0.90 1.0 0.95 1.0 0.99 1.0 1.00 1.0
minからmaxまで1であることを確認
値を0に変更
df["FLG"] = 0
df.columns
Index(['OBS.', 'TOWN', 'TOWN#', 'TRACT', 'LON', 'LAT', 'MEDV', 'CMEDV', 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'FLG'], dtype='object')
FLGカラムはそのまま
df.loc[:,["FLG"]]
FLG 0 0 1 0 2 0 3 0 4 0 ... ... 501 0 502 0 503 0 504 0 505 0 506 rows × 1 columns
df.loc[:,["FLG"]].quantile([0,0.1,0.25,0.5,0.75,0.9,0.95,0.99,1])
FLG 0.00 0.0 0.10 0.0 0.25 0.0 0.50 0.0 0.75 0.0 0.90 0.0 0.95 0.0 0.99 0.0 1.00 0.0
minからmaxまで0であることを確認。
無事値を変更できたようです。
② 特定条件を加味した列の追加
③ 単純条件
例: 特定カラムを全て2倍した値、2つのカラムの差分など
df["RM2"] = df["RM"] * 2
df[["RM","RM2"]]
RM RM2 0 6.575 13.150 1 6.421 12.842 2 7.185 14.370 3 6.998 13.996 4 7.147 14.294 ... ... ... 501 6.593 13.186 502 6.120 12.240 503 6.976 13.952 504 6.794 13.588 505 6.030 12.060 506 rows × 2 columns
きちんと2倍になっているようです。とても簡単です。
④ 条件分岐あり (if-else)
例: 数値がマイナスだったら0それ以外は1など
np.where()メソッドを利用
Return elements chosen from x or y depending on condition.
引用: numpy.where
import numpy as np
df["FLG2"] = np.where(df["TOWN"] == "Swampscott", 1, 0)
df["FLG2"]
0 0 1 1 2 1 3 0 4 0 .. 501 0 502 0 503 0 504 0 505 0 Name: FLG2, Length: 506, dtype: int64
df.columns
Index(['OBS.', 'TOWN', 'TOWN#', 'TRACT', 'LON', 'LAT', 'MEDV', 'CMEDV', 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'FLG', 'FLG2'], dtype='object')
df.groupby("FLG2").count()["TOWN"]
FLG2 0 504 1 2 Name: TOWN, dtype: int64
np.where(df["RM"] >= 7.5, "大", "中小")
array(['中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '大', '大',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '大', '大', '中小',
'中小', '大', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '大', '中小', '中小', '中小', '中小', '中小', '大',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '大', '中小', '中小',
'中小', '中小', '中小', '中小', '大', '大', '大', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '大', '大', '大', '中小', '大', '中小', '中小', '中小',
'大', '大', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '大',
'中小', '中小', '中小', '大', '中小', '中小', '中小', '大', '大', '中小', '中小',
'中小', '中小', '大', '中小', '中小', '中小', '中小', '中小', '大', '中小', '中小',
'中小', '中小', '中小', '中小', '大', '中小', '大', '大', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'大', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
'中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小'],
dtype='<U2'
)
df["FLG2_2"] = np.where(df["RM"] >= 7.5, "大", "中小")
df.groupby("FLG2_2").count()["TOWN"]
FLG2_2 中小 479 大 27 Name: TOWN, dtype: int64
np.where((df["RM"] >= 7.5) & (df["RM"] <= 8.0), 1, 0)
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
df["FLG2_3"] = np.where((df["RM"] >= 7.5) & (df["RM"] <= 8.0), 1, 0)
df.groupby("FLG2_3").count()["TOWN"]
FLG2_3 0 492 1 14 Name: TOWN, dtype: int64
⑤-① 条件分岐あり (if-elif-else)
applyメソッドを利用
Apply a function along an axis of the DataFrame.
引用: pandas.DataFrame.apply
def set_town_kbn(row):
if (row.TOWN == 'Swampscott'):
return "Swampscott"
elif (row.TOWN == 'Marblehead'):
return "Marblehead"
else:
return "OTHERS"
if文を作成するので自由にカスタマイズ出来ます。
print(df["TOWN"].loc[0])
set_town_kbn(df.loc[0])
Nahant 'OTHERS'
一番最初の行を渡してみました。
町名はNahantなので、OTHERSが返ってきました。
Swampscottでも、Marbleheadでもないので想定通りです。
print(df["TOWN"].loc[1])
set_town_kbn(df.loc[1])
Swampscott 'Swampscott'
DataFrameの2行目を関数に渡してみました。
町名はSwampscottなので、Swampscottが返ってきました。
こちらも想定通りです。
print(df["TOWN"].loc[4])
set_town_kbn(df.loc[4])
Marblehead 'Marblehead'
DataFrameの5行目のテストです。
Marbleheadなので、Marbleheadが返ってきました。
想定通り。全てのパターンで想定通りの結果が返ってきました。
df["FLG3"] = df.apply(set_town_kbn, axis = 1)
set_town_kbn関数をDataFrameに適用してみます。
df.groupby("FLG3").count()["TOWN"]
FLG3 Marblehead 3 OTHERS 501 Swampscott 2 Name: TOWN, dtype: int64
想定通りの結果になっていそうです。
⑤-② 複合条件分岐あり (if-elif-else)
&や|を使い少し複雑な条件で分岐させてみようと思います。
def set_rm_kbn(row):
if (row.TOWN == 'Swampscott' & row.RM >= 8):
return "大"
elif (row.TOWN == 'Swampscott' & row.RM >= 6 & row.RM < 8):
return "Swampscott小中"
elif (row.RM < 8):
return "Unknown小中"
else:
return "OTHERS"
# 辞書作成
adict = {'TOWN':'Swampscott','RM': 7}
# Series作成
aseries = pd.Series(data=adict)
aseries
TOWN Swampscott RM 7 dtype: object
set_rm_kbn(aseries)
TypeError Traceback (most recent call last) Input In [35], in----> 1 set_rm_kbn(aseries) Input In [31], in set_rm_kbn(row) 1 def set_rm_kbn(row): ----> 2 if (row.TOWN == 'Swampscott' & row.RM >= 8): 3 return "大" 4 elif (row.TOWN == 'Swampscott' & row.RM >= 6 & row.RM < 8): TypeError: unsupported operand type(s) for &: 'str' and 'int'
エラーになってしまいました。
調べてみると & や | を使う場合は () で各条件を囲まないといけないようです。
参考: https://www.statology.org/cannot-perform-rand_-with-a-dtyped-int64-array-and-scalar-of-type-bool/
def set_rm_kbn(row):
if ((row.TOWN == 'Swampscott') & (row.RM >= 8)):
return "大"
elif ((row.TOWN == 'Swampscott') & (row.RM >= 6) & (row.RM < 8)):
return "Swampscott小中"
elif (row.RM < 8):
return "Unknown小中"
else:
return "OTHERS"
set_rm_kbn(aseries)
'Swampscott小中'
今度は動きました。
adict = {'TOWN': ['Swampscott','Swampscott','Swampscott','Marblehead','Marblehead','Marblehead','東京都品川区'], 'RM': [9,7,2,9,7,2,4]}
adict
{'TOWN': ['Swampscott', 'Swampscott', 'Swampscott', 'Marblehead', 'Marblehead', 'Marblehead', '東京都品川区'], 'RM': [9, 7, 2, 9, 7, 2, 4]}
df_test = pd.DataFrame(data=adict)
df_test
TOWN RM 0 Swampscott 9 1 Swampscott 7 2 Swampscott 2 3 Marblehead 9 4 Marblehead 7 5 Marblehead 2 6 東京都品川区 4
df_test.dtypes
TOWN object RM int64 dtype: object
for index, row in df_test.iterrows():
print(row["TOWN"],row["RM"],set_rm_kbn(row))
Swampscott 9 大 Swampscott 7 Swampscott小中 Swampscott 2 Unknown小中 Marblehead 9 OTHERS Marblehead 7 Unknown小中 Marblehead 2 Unknown小中 東京都品川区 4 Unknown小中
想定通りのインプットとアウトプットになっているようです。
df["FLG3_2"] = df.apply(set_rm_kbn, axis = 1)
df.groupby("FLG3_2").count()["TOWN"]
FLG3_2 OTHERS 13 Swampscott小中 2 Unknown小中 491 Name: TOWN, dtype: int64
想定通りの結果になっていると思います。
まとめ
他にもnp.select()を利用する方法やPython標準での書き方など色々な方法がありますが、
シンプルな①~⑤の方法を覚えておけば困ることはないと思います。
またapplyメソッドをnp.whereメソッドの代わりに使うことも可能です。
好みの方法を利用するで問題ないと思います。
※ 大量データに対して実施する場合はnp.whereの方が処理効率がよいとかはあるかも知れません。
私は大量データを計算する場合は、データベース側でなるべく事前に処理させることが多かったのであまり気にしてこなかったのですが、今後は処理効率もきちんと考えてコードを書いていきたいなと思います。
処理効率に関しては、最近読んだ記事ですとこちらがおすすめです。