自分のキャリアをあれこれ考えながら、Pythonで様々なデータを分析していくブログです

pandasに新しいカラムを追加する5つの方法

Python
Python
ヒノマルク
ヒノマルク

今回は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")
DataFrameの上位5件を取得
df.head()
Out[0]
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
Out[0]
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の列を追加

固定値が1の列をDataFrameに追加する
df["FLG"] = 1
カラムが追加されたか確認
df.columns
Out[0]
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カラムが追加されたようです。

FLGカラムの中身を確認
df.loc[:,["FLG"]]
Out[0]

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

FLGカラムのパーセンタイルを確認
df.loc[:,["FLG"]].quantile([0,0.1,0.25,0.5,0.75,0.9,0.95,0.99,1])
Out[0]
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に変更

値を1ではなく、0に変更する
df["FLG"] = 0
カラムを確認
df.columns
Out[0]
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"]]
Out[0]
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])
Out[0]
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つのカラムの差分など

平均部屋数の2倍の値を保持する列を作成
df["RM2"] = df["RM"] * 2
新規作成したカラムを確認
df[["RM","RM2"]]
Out[0]
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

町名がSwampscottの行は1、それ以外0にしたカラムを追加
import numpy as np
df["FLG2"] = np.where(df["TOWN"] == "Swampscott", 1, 0)
中身を確認
df["FLG2"]
Out[0]
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
Out[0]
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')
FLG2の中身の件数を確認
df.groupby("FLG2").count()["TOWN"]
Out[0]
FLG2
0    504
1      2
Name: TOWN, dtype: int64
平均部屋数が7.5以上の行は大、それ以外は中小という値の配列を作成
np.where(df["RM"] >= 7.5, "大", "中小")
Out[0]
array(['中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '大', '大',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '大', '大', '中小',
       '中小', '大', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '大', '中小', '中小', '中小', '中小', '中小', '大',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '大', '中小', '中小',
       '中小', '中小', '中小', '中小', '大', '大', '大', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '大', '大', '大', '中小', '大', '中小', '中小', '中小',
       '大', '大', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '大',
       '中小', '中小', '中小', '大', '中小', '中小', '中小', '大', '大', '中小', '中小',
       '中小', '中小', '大', '中小', '中小', '中小', '中小', '中小', '大', '中小', '中小',
       '中小', '中小', '中小', '中小', '大', '中小', '大', '大', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '大', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小',
       '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小', '中小'],
      dtype='<U2')
np.whereで作成したデータを元にカラムを追加
df["FLG2_2"] = np.where(df["RM"] >= 7.5, "大", "中小")
作成したカラムの中身の件数を確認
df.groupby("FLG2_2").count()["TOWN"]
Out[0]
FLG2_2
中小    479
大      27
Name: TOWN, dtype: int64
複合条件で作成
np.where((df["RM"] >= 7.5) & (df["RM"] <= 8.0), 1, 0)
Out[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])
複合条件で作成したデータをDataFrameに追加
df["FLG2_3"] = np.where((df["RM"] >= 7.5) & (df["RM"] <= 8.0), 1, 0)
作成したカラムの中身の件数を確認
df.groupby("FLG2_3").count()["TOWN"]
Out[0]
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

applyメソッドに渡す条件式の関数を作成
def set_town_kbn(row):
    if (row.TOWN == 'Swampscott'):
        return "Swampscott"
    elif (row.TOWN == 'Marblehead'):
        return "Marblehead"
    else:
        return "OTHERS"

if文を作成するので自由にカスタマイズ出来ます。

関数のテスト index0
print(df["TOWN"].loc[0])
set_town_kbn(df.loc[0])
Out[0]
Nahant
'OTHERS'

一番最初の行を渡してみました。
町名はNahantなので、OTHERSが返ってきました。
Swampscottでも、Marbleheadでもないので想定通りです。

関数のテスト index1
print(df["TOWN"].loc[1])
set_town_kbn(df.loc[1])
Out[0]
Swampscott
'Swampscott'

DataFrameの2行目を関数に渡してみました。
町名はSwampscottなので、Swampscottが返ってきました。
こちらも想定通りです。

関数のテスト index4
print(df["TOWN"].loc[4])
set_town_kbn(df.loc[4])
Out[0]
Marblehead
'Marblehead'

DataFrameの5行目のテストです。
Marbleheadなので、Marbleheadが返ってきました。
想定通り。全てのパターンで想定通りの結果が返ってきました。

applyメソッドを使ってみます
df["FLG3"] = df.apply(set_town_kbn, axis = 1)

set_town_kbn関数をDataFrameに適用してみます。

FLG3の中身の件数を確認
df.groupby("FLG3").count()["TOWN"]
Out[0]
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"
関数のテスト用にseriesを作成
# 辞書作成
adict = {'TOWN':'Swampscott','RM': 7}
# Series作成
aseries = pd.Series(data=adict)
中身を確認
aseries
Out[0]
TOWN    Swampscott
RM               7
dtype: object
Seriesをset_rm_kbn関数に渡す
set_rm_kbn(aseries)
Out[0]

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)
Out[0]
'Swampscott小中'

今度は動きました。

テストデータ作成
adict = {'TOWN': ['Swampscott','Swampscott','Swampscott','Marblehead','Marblehead','Marblehead','東京都品川区'], 'RM': [9,7,2,9,7,2,4]}
中身を確認
adict
Out[0]
{'TOWN': ['Swampscott',
  'Swampscott',
  'Swampscott',
  'Marblehead',
  'Marblehead',
  'Marblehead',
  '東京都品川区'],
 'RM': [9, 7, 2, 9, 7, 2, 4]}
テスト用のDataFrameを作成
df_test = pd.DataFrame(data=adict)
中身を確認
df_test
Out[0]
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
Out[0]
TOWN    object
RM       int64
dtype: object
各行にset_rm_kbn関数を適用しテスト
for index, row in df_test.iterrows():
    print(row["TOWN"],row["RM"],set_rm_kbn(row))
Out[0]
Swampscott 9 大
Swampscott 7 Swampscott小中
Swampscott 2 Unknown小中
Marblehead 9 OTHERS
Marblehead 7 Unknown小中
Marblehead 2 Unknown小中
東京都品川区 4 Unknown小中

想定通りのインプットとアウトプットになっているようです。

set_rm_kbn関数をapplyメソッドでDataFrameに適用
df["FLG3_2"] = df.apply(set_rm_kbn, axis = 1)
FLG3_2の中身の件数を確認
df.groupby("FLG3_2").count()["TOWN"]
Out[0]
FLG3_2
OTHERS           13
Swampscott小中      2
Unknown小中       491
Name: TOWN, dtype: int64

想定通りの結果になっていると思います。

スポンサーリンク

まとめ

他にもnp.select()を利用する方法やPython標準での書き方など色々な方法がありますが、
シンプルな①~⑤の方法を覚えておけば困ることはないと思います。

またapplyメソッドをnp.whereメソッドの代わりに使うことも可能です。
好みの方法を利用するで問題ないと思います。

※ 大量データに対して実施する場合はnp.whereの方が処理効率がよいとかはあるかも知れません。

私は大量データを計算する場合は、データベース側でなるべく事前に処理させることが多かったのであまり気にしてこなかったのですが、今後は処理効率もきちんと考えてコードを書いていきたいなと思います。

処理効率に関しては、最近読んだ記事ですとこちらがおすすめです。

スポンサーリンク

参照

  1. https://pandas.pydata.org/docs/getting_started/intro_tutorials/05_add_columns.html
  2. https://pandas.pydata.org/docs/user_guide/dsintro.html#basics-dataframe-sel-add-del
  3. https://www.geeksforgeeks.org/python-creating-a-pandas-dataframe-column-based-on-a-given-condition/
タイトルとURLをコピーしました