pandasによるデータ処理

Contents

16. pandasによるデータ処理#

pandasは表形式のデータを扱うために便利なPythonライブラリです. pandasは行列データを効率的に扱うNumPyをベースに実装されていることもあり,機械学習ライブラリとの相性も良いです. そのため,Pythonを用いたデータ分析を行う際に非常に良く用いられています.

この文書では,データ分析の際に実用的に用いるpandasの機能についてピックアップします.

Note

なぜpandasでデータ処理をするのか?

集約演算などを使いこなせばSQLでもデータ処理はできるのに,なぜpandasを用いるのか? それは,pandasはPythonの機械学習,統計モデリング,データ可視化のためのツールと相性が良い. また,テキスト処理や日付処理,欠損値処理などの細かな前処理は,Pythonを使った方が圧倒的に効率がよい.

しかし,pandasにも弱点がある. pandasは超大規模なデータの処理には向かない(分散・並列処理のためのPythonコードを自前で書かなければならない). 一方,SQLはその背後に関係データベースシステムがデータ処理を最適化してくれるため,大規模データも効率よく処理できる.

pandasにもSQLにもそれぞれメリットとデメリットがあるため,両者をうまく使い分ける必要がある.

  • データ分析や機械学習に必要となるデータを,SQLを使ってデータベースから抽出し

  • 抽出したデータをPandasで読み込んでデータ処理する

というのが典型的な使い分けになる.

16.1. はじめに - 表データとデータフレーム#

pandasで扱うデータは,以下のように行と列からなる表データです.

都道府県ID

都道府県名

2020年法定人口

県庁所在地

1

北海道

5224614

札幌市

2

青森県

1237984

青森市

3

岩手県

1210534

盛岡市

一般に表データはMicrosoftのExcelファイルで取り扱われることが多いです. しかし,データ分析の世界では,比較的小さな表データは,特定のソフトウェアに依存しない(互換性の高い)CSV(comma-sepprated values)ファイル,もしくはTSV(tab-separated values)ファイルで保存/配布されることが多いです. CSVファイルは表の各項目の値をカンマ(,)で区切ったテキストデータです. CSVファイルの行が表の行に相当します. CSVファイルの1行目には,表の項目名を並べることが多いです. CSVファイルの拡張子には.csvが用いられます.

以下は,上記都道府県に関する表データをCSVファイルとして保存したその中身の例です.

 都道府県ID,都道府県名,2020年法定人口,県庁所在地
 1,北海道,5224614,札幌市
 2,青森県,1237984,青森市
 3,岩手県,1210534,盛岡市
...

1行には表の項目(見出し)が,2行目以降には各都道府県に関するデータがカンマで区切られて定義されています. 上記ファイルはCSVファイルなので,各データがカンマで区切られています. この表データをTSVファイルで保存する場合は,カンマではなくタブ記号(\t)でデータを区切ります. TSVファイルの拡張子は.tsvです.

pandasライブラリでは,表形式のデータをデータフレーム(DataFrame) という形式に変換してデータの前処理や分析を行います. データフレームとはPythonで計算処理を効率的に行うための表データだと思ってもらえばOKです. 以下,pandasの扱い方について説明します.

16.2. ライブラリの準備#

pandasはPythonの標準ライブラリではありません. お手元の計算環境にpandasライブラリがない場合は,以下のコマンドでpandasをインストールしてください.

pip install pandas

Jupyterを用いている場合は,セルに以下を書いて実行してください.

try:
    import pandas as pd
except:
    !pip install pandas
    import pandas as pd

pandasをインストール後,以下のコードを実行してpandasライブラリを読み込みます.

import pandas as pd
# 警告文を非表示に
import warnings
warnings.filterwarnings('ignore')

16.3. データの読み込み#

16.3.1. CSV/TSVファイルを読み込む#

Pythonで表データを分析する場合,誰かが作成したCSV/TSVファイルをpandasライブラリで読み込むのが典型的なシナリオです. このURLタイタニック号の乗船客に関するCSVファイルがあります. こちらをダウンロードしてpandasで読み込んでみましょう. ダウンロードしたファイルはdatasetディレクトリにtitanic_train.csvという名前で保存したとします.

pandasで表データを読み込むにはread_table関数を用います. 以下はdatasetディレクトリにあるtitanic_train.csvを読み込み,ファイルの中身をデータフレームに変換したものを変数dfに格納するコードです. 関数の1つ目の引数にファイルの保存先を指定します. sepという引数では,読み込んだファイルに用いられている区切り文字を指定しています. 今回読み込むファイルはCSVファイルなのでカンマ(,)を指定しています. TSVファイルを読み込む場合はタブ文字(\t)を指定します.

df = pd.read_table('dataset/titanic_train.csv', sep=',')

変数dfを表示してみましょう. Jupyterを用いている場合,下記コードを実行するとdfの中身(の一部)が表示されます. データフレーム形式のデータが得られていることが分かります.

df
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
... ... ... ... ... ... ... ... ... ... ... ... ...
886 887 0 2 Montvila, Rev. Juozas male 27.0 0 0 211536 13.0000 NaN S
887 888 1 1 Graham, Miss. Margaret Edith female 19.0 0 0 112053 30.0000 B42 S
888 889 0 3 Johnston, Miss. Catherine Helen "Carrie" female NaN 1 2 W./C. 6607 23.4500 NaN S
889 890 1 1 Behr, Mr. Karl Howell male 26.0 0 0 111369 30.0000 C148 C
890 891 0 3 Dooley, Mr. Patrick male 32.0 0 0 370376 7.7500 NaN Q

891 rows × 12 columns

先ほどはダウンロードしたファイルを読み込みましたが,read_table関数はURLを指定することでファイルのダウンロードと読み込みを一括して行ってくれます. 先ほどのコードを以下に書き換えると,指定したURL上のファイルを読み込み,データフレームを作成します.

df = pd.read_table('https://raw.githubusercontent.com/hontolab-courses/dmml-2022/main/dataset/titanic_train.csv', sep=',')

read_table関数は引数に様々なオプションを指定できます(詳しくはドキュメントを参考). 代表的な引数は以下の通りです:

  • sep: 区切り文字

  • header: 指定ファイルの中で見出し項目が格納された行番号(デフォルトは0)

  • encoding: ファイルの文字コード

例えば,こちらのURLで公開されている独立行政法人統計センター作成の教育用標準データセットSSDSE-基本素材(SSDSE-E)の表データを読み込んでみましょう.

このデータはCSVファイルに格納されていますが,文字コードとしてSHIFT-JISが用いられています. また,表の見出しは3行目で定義されており,4行目以降に実データが記述されています. このことを踏まえて,SSDSE-Eのデータをデータフレームとして読み込むコードは以下となります.

# CSVファイルの1行目をpandasではゼロ行目と読むので,3行目のデータを見出しとして指定する場合はheader=2となる
df = pd.read_table('https://www.nstac.go.jp/sys/files/SSDSE-E-2024.csv', sep=',', header=2, encoding='shift-jis')

16.3.2. 関係データベースへの問い合わせ結果を読み込む#

pandasには,MySQLやSQLiteなどの関係データベースに対する問い合わせ結果をDataFrameオブジェクトとして読み込むread_sql関数がある. read_sql関数は第1引数にSQL文,第2引数にデータベースにアクセスするための「データベースエンジン」を指定する.

データベースエンジンの生成にはSQLAlchemyライブラリが便利. エンジンの生成は以下のように行う.

import sqlalchemy

# SQLiteを用いる場合
# データベースファイルがdataset/SSDSE.dbにあると仮定
db_path = 'dataset/SSDSE.db'
engine = sqlalchemy.create_engine(f'sqlite:///{db_path}')

# MySQLを用いる場合
# mysql_user = 'sample_user'
# mysql_passwd = 'password'
# mysql_host = 'localhost'
# mysql_port = 3306
# db_name = 'SSDSE'
# db_url = f'mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{db_name}?charset=utf8'
# engine = sqlalchemy.create_engine(db_url)

read_sql関数にSQL文とエンジンを指定すれば,DataFrameオブジェクトに変換されたデータベースへの問い合わせ結果が得られる. 今仮に,独立行政法人統計センターが公開している教育用標準データセット(SSDSE)の基本素材SSDSE-E(データの解説はこちら)から抜粋・加工したデータが,SSDSEデータベースの中にpopulationというテーブルで格納されていたとする. 以下は,そのテーブルにある全レコードを取得するSQLの結果をDataFrameオブジェクトとして得るコード例である.

sql = 'SELECT * FROM population;'
pd.read_sql(sql, engine)
地域コード 都道府県 調査年度 総人口 小学校児童数 中学校生徒数 高等学校生徒数 大学学生数
0 R01000 北海道 2021 5183000 231714 122742 115335 79729
1 R02000 青森県 2021 1221000 54460 29940 30543 15419
2 R03000 岩手県 2021 1196000 55597 30269 29980 11340
3 R04000 宮城県 2021 2290000 112246 58748 55329 49580
4 R05000 秋田県 2021 945000 38992 21924 21448 8904
... ... ... ... ... ... ... ... ...
89 R43000 熊本県 2020 1738301 96934 48218 45401 24771
90 R44000 大分県 2020 1123852 57705 29212 29937 15278
91 R45000 宮崎県 2020 1069576 60450 30211 29590 9924
92 R46000 鹿児島県 2020 1588256 89738 44912 43928 15432
93 R47000 沖縄県 2020 1467480 101918 48763 44037 17932

94 rows × 8 columns

16.4. データの確認#

以下,上で読み込んだ独立行政法人統計センター作成の教育用標準データセットSSDSE-基本素材(SSDSE-E)の表データがデータフレームに変換の上,変数dfに格納されているとの前提で説明します.

16.4.1. 表の行数,列数の確認#

DataFrameオブジェクトのプロパティshapeにアクセスすると行数と列数のタプルが得られます.

df.shape
(48, 92)

16.4.2. 表の行数#

表データの行数だけを得たいときは,len関数を用います.

len(df)
48

16.4.3. 表の項目情報#

表データの項目名を取得したい場合は,DataFrameオブジェクトのプロパティcolumnsにアクセスします. columnsにアクセスすると,項目名のリストを得られます.

df.columns
Index(['地域コード', '都道府県', '総人口', '日本人人口', '15歳未満人口', '15〜64歳人口', '65歳以上人口',
       '外国人人口', '出生数', '合計特殊出生率', '死亡数', '転入者数(日本人移動者)', '転出者数(日本人移動者)',
       '一般世帯数', '一般世帯人員数', '単独世帯数', '婚姻件数', '離婚件数', '総面積(北方地域及び竹島を除く)',
       '可住地面積', '自然公園面積', '県内総生産額(平成27年基準)', '県民所得(平成27年基準)',
       '1人当たり県民所得(平成27年基準)', '事業所数(民営)', '事業所数(民営)(建設業)', '事業所数(民営)(製造業)',
       '事業所数(民営)(情報通信業)', '事業所数(民営)(卸売業、小売業)', '事業所数(民営)(宿泊業、飲食サービス業)',
       '事業所数(民営)(生活関連サービス業、娯楽業)', '事業所数(民営)(医療、福祉)', '従業者数(民営)',
       '従業者数(民営)(建設業)', '従業者数(民営)(製造業)', '従業者数(民営)(情報通信業)',
       '従業者数(民営)(卸売業、小売業)', '従業者数(民営)(宿泊業、飲食サービス業)', '従業者数(民営)(生活関連サービス業、娯楽業)',
       '従業者数(民営)(医療、福祉)', '農家数(販売農家)', '農家数(自給的農家)', '耕地面積', '旅館営業施設数(ホテルを含む)',
       '旅館営業施設客室数(ホテルを含む)', '幼稚園数', '幼稚園在園者数', '小学校数', '小学校児童数', '中学校数',
       '中学校生徒数', '高等学校数', '高等学校生徒数', '短期大学数', '大学数', '短期大学学生数', '大学学生数',
       '公民館数', '図書館数', '博物館数', '劇場、音楽堂等数', '社会体育施設数', '民間体育施設数', '映画館数',
       '一般旅券発行件数', '延べ宿泊者数', '外国人延べ宿泊者数', '総住宅数', '空き家数', '持ち家数', '一戸建住宅数',
       '1住宅当たり延べ面積', '総人口(非水洗化人口+水洗化人口)', '非水洗化人口', 'ごみ総排出量(総量)',
       '1人1日当たりの排出量', 'ごみのリサイクル率', '小売店数', '飲食店数', '大型小売店数', '一般病院数', '一般診療所数',
       '歯科診療所数', '医師数', '歯科医師数', '薬剤師数', '保育所等数', '保育所等在所児数', '消費支出(二人以上の世帯)',
       '食料費(二人以上の世帯)', '住居費(二人以上の世帯)', '教養娯楽費(二人以上の世帯)'],
      dtype='object')

DataFrameオブジェクトのメソッドinfoを用いると,表の各項目の名前に加えて型情報や欠損していないデータの数などの情報が得られます.

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 48 entries, 0 to 47
Data columns (total 92 columns):
 #   Column                   Non-Null Count  Dtype  
---  ------                   --------------  -----  
 0   地域コード                    48 non-null     object 
 1   都道府県                     48 non-null     object 
 2   総人口                      48 non-null     int64  
 3   日本人人口                    48 non-null     int64  
 4   15歳未満人口                  48 non-null     int64  
 5   15〜64歳人口                 48 non-null     int64  
 6   65歳以上人口                  48 non-null     int64  
 7   外国人人口                    48 non-null     int64  
 8   出生数                      48 non-null     int64  
 9   合計特殊出生率                  48 non-null     float64
 10  死亡数                      48 non-null     int64  
 11  転入者数(日本人移動者)             48 non-null     int64  
 12  転出者数(日本人移動者)             48 non-null     int64  
 13  一般世帯数                    48 non-null     int64  
 14  一般世帯人員数                  48 non-null     int64  
 15  単独世帯数                    48 non-null     int64  
 16  婚姻件数                     48 non-null     int64  
 17  離婚件数                     48 non-null     int64  
 18  総面積(北方地域及び竹島を除く)         48 non-null     int64  
 19  可住地面積                    48 non-null     int64  
 20  自然公園面積                   48 non-null     int64  
 21  県内総生産額(平成27年基準)          48 non-null     int64  
 22  県民所得(平成27年基準)            48 non-null     int64  
 23  1人当たり県民所得(平成27年基準)       48 non-null     int64  
 24  事業所数(民営)                 48 non-null     int64  
 25  事業所数(民営)(建設業)            48 non-null     int64  
 26  事業所数(民営)(製造業)            48 non-null     int64  
 27  事業所数(民営)(情報通信業)          48 non-null     int64  
 28  事業所数(民営)(卸売業、小売業)        48 non-null     int64  
 29  事業所数(民営)(宿泊業、飲食サービス業)    48 non-null     int64  
 30  事業所数(民営)(生活関連サービス業、娯楽業)  48 non-null     int64  
 31  事業所数(民営)(医療、福祉)          48 non-null     int64  
 32  従業者数(民営)                 48 non-null     int64  
 33  従業者数(民営)(建設業)            48 non-null     int64  
 34  従業者数(民営)(製造業)            48 non-null     int64  
 35  従業者数(民営)(情報通信業)          48 non-null     int64  
 36  従業者数(民営)(卸売業、小売業)        48 non-null     int64  
 37  従業者数(民営)(宿泊業、飲食サービス業)    48 non-null     int64  
 38  従業者数(民営)(生活関連サービス業、娯楽業)  48 non-null     int64  
 39  従業者数(民営)(医療、福祉)          48 non-null     int64  
 40  農家数(販売農家)                48 non-null     int64  
 41  農家数(自給的農家)               48 non-null     int64  
 42  耕地面積                     48 non-null     int64  
 43  旅館営業施設数(ホテルを含む)          48 non-null     int64  
 44  旅館営業施設客室数(ホテルを含む)        48 non-null     int64  
 45  幼稚園数                     48 non-null     int64  
 46  幼稚園在園者数                  48 non-null     int64  
 47  小学校数                     48 non-null     int64  
 48  小学校児童数                   48 non-null     int64  
 49  中学校数                     48 non-null     int64  
 50  中学校生徒数                   48 non-null     int64  
 51  高等学校数                    48 non-null     int64  
 52  高等学校生徒数                  48 non-null     int64  
 53  短期大学数                    48 non-null     int64  
 54  大学数                      48 non-null     int64  
 55  短期大学学生数                  48 non-null     int64  
 56  大学学生数                    48 non-null     int64  
 57  公民館数                     48 non-null     int64  
 58  図書館数                     48 non-null     int64  
 59  博物館数                     48 non-null     int64  
 60  劇場、音楽堂等数                 48 non-null     int64  
 61  社会体育施設数                  48 non-null     int64  
 62  民間体育施設数                  48 non-null     int64  
 63  映画館数                     48 non-null     int64  
 64  一般旅券発行件数                 48 non-null     int64  
 65  延べ宿泊者数                   48 non-null     int64  
 66  外国人延べ宿泊者数                48 non-null     int64  
 67  総住宅数                     48 non-null     int64  
 68  空き家数                     48 non-null     int64  
 69  持ち家数                     48 non-null     int64  
 70  一戸建住宅数                   48 non-null     int64  
 71  1住宅当たり延べ面積               48 non-null     float64
 72  総人口(非水洗化人口+水洗化人口)        48 non-null     int64  
 73  非水洗化人口                   48 non-null     int64  
 74  ごみ総排出量(総量)               48 non-null     int64  
 75  1人1日当たりの排出量              48 non-null     int64  
 76  ごみのリサイクル率                48 non-null     float64
 77  小売店数                     48 non-null     int64  
 78  飲食店数                     48 non-null     int64  
 79  大型小売店数                   48 non-null     int64  
 80  一般病院数                    48 non-null     int64  
 81  一般診療所数                   48 non-null     int64  
 82  歯科診療所数                   48 non-null     int64  
 83  医師数                      48 non-null     int64  
 84  歯科医師数                    48 non-null     int64  
 85  薬剤師数                     48 non-null     int64  
 86  保育所等数                    48 non-null     int64  
 87  保育所等在所児数                 48 non-null     int64  
 88  消費支出(二人以上の世帯)            48 non-null     int64  
 89  食料費(二人以上の世帯)             48 non-null     int64  
 90  住居費(二人以上の世帯)             48 non-null     int64  
 91  教養娯楽費(二人以上の世帯)           48 non-null     int64  
dtypes: float64(3), int64(87), object(2)
memory usage: 34.6+ KB

16.4.4. 基本統計量#

DataFrameオブジェクトのメソッドdescribeを用いると,表の各項目の基本統計量が得られます. 得られる基本統計量は以下の通りです:

  • データ数

  • 平均

  • 標準偏差

  • 最小値,最大値

  • 中央値

  • 第1四分位数,第3四分位数

df.describe()
総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 死亡数 転入者数(日本人移動者) ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
count 4.800000e+01 4.800000e+01 4.800000e+01 4.800000e+01 4.800000e+01 4.800000e+01 48.000000 48.000000 4.800000e+01 4.800000e+01 ... 48.000000 48.000000 48.000000 48.000000 48.000000 4.800000e+01 48.000000 48.000000 48.00000 48.000000
mean 5.206104e+06 5.084646e+06 6.043125e+05 3.092000e+06 1.509812e+06 1.001025e+05 33817.354167 1.397708 5.997579e+04 9.397342e+04 ... 2829.125000 14150.958333 4476.791667 13415.916667 1249.750000 1.101331e+05 289656.083333 76615.958333 19231.12500 25987.687500
std 1.786588e+07 1.744375e+07 2.072707e+06 1.063209e+07 5.164664e+06 3.497592e+05 116144.047847 0.145225 2.051223e+05 3.259851e+05 ... 9765.277128 48665.779507 15458.270437 46353.143907 4278.561169 3.769878e+05 18982.531256 5518.803196 4783.81712 3771.185339
min 5.440000e+05 5.390000e+05 6.600000e+04 2.980000e+05 1.800000e+05 3.651000e+03 3708.000000 1.080000 7.605000e+03 7.967000e+03 ... 254.000000 1871.000000 369.000000 1229.000000 185.000000 1.640300e+04 245054.000000 67889.000000 6220.00000 17601.000000
25% 1.049250e+06 1.041250e+06 1.205000e+05 5.690000e+05 3.500000e+05 9.606750e+03 6409.750000 1.300000 1.366800e+04 1.544575e+04 ... 489.500000 2867.250000 738.000000 2424.000000 298.750000 2.488425e+04 276968.250000 72335.500000 16401.00000 22929.250000
50% 1.640500e+06 1.619500e+06 2.025000e+05 9.135000e+05 5.270000e+05 1.567700e+04 11108.000000 1.400000 2.180900e+04 2.477100e+04 ... 804.000000 4348.500000 1277.500000 3524.000000 447.500000 3.953300e+04 289323.000000 75822.500000 19698.50000 25953.500000
75% 2.780000e+06 2.722750e+06 3.257500e+05 1.609250e+06 8.355000e+05 5.268950e+04 17035.500000 1.467500 3.228400e+04 4.818850e+04 ... 1413.000000 7921.500000 2151.000000 6954.000000 684.500000 5.955550e+04 301817.000000 80910.250000 22282.00000 28304.500000
max 1.249470e+08 1.220310e+08 1.450300e+07 7.420800e+07 3.623600e+07 2.402460e+06 811622.000000 1.800000 1.439856e+06 2.255362e+06 ... 67899.000000 339623.000000 107443.000000 321982.000000 29994.000000 2.643196e+06 324793.000000 87973.000000 30323.00000 35050.000000

8 rows × 90 columns

16.5. データフレームへのアクセス#

16.5.1. 先頭・末尾のレコード(行)の取得#

DataFrameオブジェクトのメソッドheadは,データフレームの先頭\(N\)件のレコードを返す. 引数に何も指定しないと5件返す.

df.head()
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
0 R00000 全国 124947000 122031000 14503000 74208000 36236000 2402460 811622 1.30 ... 67899 339623 107443 321982 29994 2643196 290865 77474 18645 26642
1 R01000 北海道 5140000 5098000 530000 2924000 1686000 34321 28762 1.20 ... 2818 13731 4418 11802 1075 76885 277737 73037 24873 27234
2 R02000 青森県 1204000 1198000 123000 663000 419000 5409 6513 1.31 ... 505 2773 735 2345 472 30738 249660 73725 10541 20068
3 R03000 岩手県 1181000 1173000 125000 648000 408000 6937 6472 1.30 ... 557 2700 1016 2536 393 28580 285815 77251 18814 25733
4 R04000 宮城県 2280000 2256000 258000 1363000 659000 19453 13761 1.15 ... 1051 5950 1896 5502 506 40519 287781 78589 22951 26516

5 rows × 92 columns

DataFrameオブジェクトのメソッドtailは,データフレームの末尾\(N\)件のレコードを返す. 引数に何も指定しないと5件返す.

df.tail()
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
43 R43000 熊本県 1718000 1699000 223000 943000 552000 14591 12670 1.59 ... 835 5415 1377 4036 623 52158 281836 70132 30323 24428
44 R44000 大分県 1107000 1092000 131000 600000 376000 10168 7327 1.54 ... 530 3370 740 2317 335 26578 298060 75218 18820 28260
45 R45000 宮崎県 1052000 1044000 136000 565000 352000 6474 7590 1.64 ... 493 2879 731 2272 420 30732 271613 70162 15433 21950
46 R46000 鹿児島県 1563000 1550000 201000 838000 523000 10037 11618 1.65 ... 795 4653 1352 3266 582 40357 279101 70070 20822 22440
47 R47000 沖縄県 1468000 1446000 240000 884000 344000 18157 14535 1.80 ... 607 3887 885 2432 614 57108 251735 68318 25189 18429

5 rows × 92 columns

16.5.2. 射影#

表データから指定した列のみに注目してデータを抽出する操作,関係データベースの分野では射影と呼ぶ.

pandasのDataFrameで射影を行う方法は2種類ある. 1つ目は注目したい列項目名をドット(.)で指定する方法である. 以下は,データフレームdfから列「総人口」にあるデータを抽出するコードである.

df.総人口
0     124947000
1       5140000
2       1204000
3       1181000
4       2280000
        ...    
43      1718000
44      1107000
45      1052000
46      1563000
47      1468000
Name: 総人口, Length: 48, dtype: int64

もう1つの射影方法は中括弧の中で列項目名を指定する方法である. この方法では,注目したい列名を文字列もしくは文字列のリストで指定する. 以下は,データフレームdfから列「総人口」にあるデータを抽出するコードである.

df['総人口']
0     124947000
1       5140000
2       1204000
3       1181000
4       2280000
        ...    
43      1718000
44      1107000
45      1052000
46      1563000
47      1468000
Name: 総人口, Length: 48, dtype: int64

ドットを用いる方法は簡便であるが,列項目を複数指定して射影できない. 中括弧を用いる射影方法では,中括弧の中に文字列のリストを与えることで,複数の列項目に注目してデータを抽出できる. 以下は,データフレームdfから「地域コード」「都道府県」「総人口」の列のデータを抽出するコードである.

target_columns = ['地域コード', '都道府県', '総人口']
df[target_columns]

# 以下のように書いても問題ない
# df['地域コード', '都道府県', '総人口']
地域コード 都道府県 総人口
0 R00000 全国 124947000
1 R01000 北海道 5140000
2 R02000 青森県 1204000
3 R03000 岩手県 1181000
4 R04000 宮城県 2280000
... ... ... ...
43 R43000 熊本県 1718000
44 R44000 大分県 1107000
45 R45000 宮崎県 1052000
46 R46000 鹿児島県 1563000
47 R47000 沖縄県 1468000

48 rows × 3 columns

16.5.3. データフレームに対する四則演算#

pandasのDataFrameオブジェクトから任意の列を射影しスカラーの四則演算を適用すると,射影した列データ全体に演算が適用される. 例えば,以下はデータフレームdf総人口列の各値に100を加算するコード例である.

# 総人口列の各値に100が加算された列が返る
df.総人口 + 100
0     124947100
1       5140100
2       1204100
3       1181100
4       2280100
        ...    
43      1718100
44      1107100
45      1052100
46      1563100
47      1468100
Name: 総人口, Length: 48, dtype: int64

列と列の長さ(要素数)が同じなら,列同士の四則演算を行うことができる. 2つの列同士の四則演算を行うと,双方の列の各行の値を四則演算した結果の列が返る.

以下は,データフレームdf日本人人口列の値を総人口列の値で割る(つまり,日本人の割合を求める)コード例である.

df['日本人人口'] / df['総人口']
0     0.976662
1     0.991829
2     0.995017
3     0.993226
4     0.989474
        ...   
43    0.988941
44    0.986450
45    0.992395
46    0.991683
47    0.985014
Length: 48, dtype: float64

16.5.4. 任意の行にあるレコードの取得#

df[i:j]と書くと,データフレームdfi件目(ゼロを起点)からj-1件目のレコードを取得できる. 以下はデータフレームdfの2件目から3件目まで(4件目は含まれない)のレコードを取得するコードである.

df[2:4]
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
2 R02000 青森県 1204000 1198000 123000 663000 419000 5409 6513 1.31 ... 505 2773 735 2345 472 30738 249660 73725 10541 20068
3 R03000 岩手県 1181000 1173000 125000 648000 408000 6937 6472 1.30 ... 557 2700 1016 2536 393 28580 285815 77251 18814 25733

2 rows × 92 columns

DataFrameオブジェクトのlocメソッドを用いると,任意の行にある任意の列の値を取得することができる. 以下はデータフレームdfの2件目から3件目のレコードについて,「地域コード」「都道府県」「総人口」の列の値だけ抽出(射影)するコードである.

target_columns = ['地域コード', '都道府県', '総人口']
df.loc[2:4, target_columns]
地域コード 都道府県 総人口
2 R02000 青森県 1204000
3 R03000 岩手県 1181000
4 R04000 宮城県 2280000

上記コードは,以下と同じ(射影後に取得行を絞る).

target_columns = ['地域コード', '都道府県', '総人口']
df[target_columns].loc[2:4]
地域コード 都道府県 総人口
2 R02000 青森県 1204000
3 R03000 岩手県 1181000
4 R04000 宮城県 2280000

以下のようにlocを書くと,すべての列の値を取得することができる.

# データフレーム`df`の2件目から3件目のレコードについて,すべての列の値を抽出(射影)する
df.loc[2:4, :]
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
2 R02000 青森県 1204000 1198000 123000 663000 419000 5409 6513 1.31 ... 505 2773 735 2345 472 30738 249660 73725 10541 20068
3 R03000 岩手県 1181000 1173000 125000 648000 408000 6937 6472 1.30 ... 557 2700 1016 2536 393 28580 285815 77251 18814 25733
4 R04000 宮城県 2280000 2256000 258000 1363000 659000 19453 13761 1.15 ... 1051 5950 1896 5502 506 40519 287781 78589 22951 26516

3 rows × 92 columns

上の例ではi件目からj件目までのように連続するレコードを取得していたが,locメソッドでは具体的なレコードの行番号を指定してレコードを取得することもできる. 以下は,データフレームdfの1件目と11件目と21件目のレコードを抽出(射影)するコードである.

target_rows = [1, 11, 21]
df.loc[target_rows, :]

# 以下のように書いても問題ない
# df.loc[[1, 11, 21], :]
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
1 R01000 北海道 5140000 5098000 530000 2924000 1686000 34321 28762 1.20 ... 2818 13731 4418 11802 1075 76885 277737 73037 24873 27234
11 R11000 埼玉県 7337000 7136000 847000 4483000 2007000 161439 45424 1.22 ... 3550 13604 5575 16370 1474 123008 324793 87922 21820 35050
21 R21000 岐阜県 1946000 1888000 231000 1111000 604000 48979 11730 1.40 ... 959 4580 1735 4060 415 38709 313314 77357 13720 27226

3 rows × 92 columns

16.5.5. 絞り込み#

データフレームから特定の条件を満たすレコードのみを取得するには2種類の方法がある:

  • 中括弧の中で条件を指定する方法

  • queryメソッドを使用する方法

データフレームdfにあるレコードの中で,「総人口」の値が700万以上になるものだけを抽出する例を考えてみよう. 中括弧の中で条件を指定してデータフレームにアクセスすると,条件にマッチするレコードだけが絞り込まれる. 条件指定には射影を用いる.

df[df['総人口'] >= 7000000]

# ドット表現を用いて条件を指定することも可能
# df[df.総人口 >=  >= 7000000]
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
0 R00000 全国 124947000 122031000 14503000 74208000 36236000 2402460 811622 1.30 ... 67899 339623 107443 321982 29994 2643196 290865 77474 18645 26642
11 R11000 埼玉県 7337000 7136000 847000 4483000 2007000 161439 45424 1.22 ... 3550 13604 5575 16370 1474 123008 324793 87922 21820 35050
13 R13000 東京都 14038000 13443000 1535000 9301000 3202000 483372 95404 1.08 ... 10678 48072 17245 52842 3523 290125 321633 87973 29988 33099
14 R14000 神奈川県 9232000 8991000 1053000 5797000 2383000 195535 58836 1.22 ... 4984 21377 7605 23872 2012 165014 301379 85076 23065 29394
23 R23000 愛知県 7495000 7228000 948000 4628000 1920000 231369 53918 1.41 ... 3718 17842 6159 16003 1558 148009 319344 79757 22575 30894
27 R27000 大阪府 8782000 8524000 1002000 5349000 2432000 208681 59780 1.27 ... 5442 26431 8184 27297 1573 172365 265161 80890 18350 25978

6 rows × 92 columns

df[[True, False, True, False] * 12]
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
0 R00000 全国 124947000 122031000 14503000 74208000 36236000 2402460 811622 1.30 ... 67899 339623 107443 321982 29994 2643196 290865 77474 18645 26642
2 R02000 青森県 1204000 1198000 123000 663000 419000 5409 6513 1.31 ... 505 2773 735 2345 472 30738 249660 73725 10541 20068
4 R04000 宮城県 2280000 2256000 258000 1363000 659000 19453 13761 1.15 ... 1051 5950 1896 5502 506 40519 287781 78589 22951 26516
6 R06000 山形県 1041000 1033000 113000 566000 362000 7149 5898 1.32 ... 473 2608 678 2129 299 23969 276567 77493 16140 22348
8 R08000 茨城県 2840000 2767000 321000 1655000 864000 57819 16502 1.30 ... 1378 5838 1979 6704 627 56249 298053 71578 18805 25789
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
38 R38000 愛媛県 1306000 1294000 147000 716000 443000 11159 8011 1.40 ... 658 3847 943 3024 313 24543 245054 67889 17559 22447
40 R40000 福岡県 5116000 5030000 654000 3013000 1449000 66699 37540 1.37 ... 3068 16784 5672 12714 1054 117582 286265 75957 20020 28318
42 R42000 長崎県 1283000 1272000 158000 690000 435000 8316 8862 1.60 ... 709 4399 1203 2954 492 35679 271129 73489 23124 17601
44 R44000 大分県 1107000 1092000 131000 600000 376000 10168 7327 1.54 ... 530 3370 740 2317 335 26578 298060 75218 18820 28260
46 R46000 鹿児島県 1563000 1550000 201000 838000 523000 10037 11618 1.65 ... 795 4653 1352 3266 582 40357 279101 70070 20822 22440

24 rows × 92 columns

Note

Q. df.総人口 >= 7000000 を実行すると何が返ってくるのか?

実行してみると分かるが,dfの各レコードの総人口が700万以上かそうでないかをTrueFalseで表したpandas.Series(リストのようなもの)が返ってくる. 返ってくるpandas.Seriesでは,nつ目の要素がdfのn番目のレコードがTrueFalseを表している. pandasのDataFrameオブジェクトは中括弧の中にTrue/Falseのリスト(もしくはpandas.Series)を入れると,値がTrueのレコードだけをDataFrameオブジェクトから抽出して返す. この振る舞いを利用して,pandasではレコードの絞り込みを行うのである.

同じことをDataFrameオブジェクトのqueryメソッドを使って書くと,以下のようになる.

df.query('総人口 >= 7000000')
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
0 R00000 全国 124947000 122031000 14503000 74208000 36236000 2402460 811622 1.30 ... 67899 339623 107443 321982 29994 2643196 290865 77474 18645 26642
11 R11000 埼玉県 7337000 7136000 847000 4483000 2007000 161439 45424 1.22 ... 3550 13604 5575 16370 1474 123008 324793 87922 21820 35050
13 R13000 東京都 14038000 13443000 1535000 9301000 3202000 483372 95404 1.08 ... 10678 48072 17245 52842 3523 290125 321633 87973 29988 33099
14 R14000 神奈川県 9232000 8991000 1053000 5797000 2383000 195535 58836 1.22 ... 4984 21377 7605 23872 2012 165014 301379 85076 23065 29394
23 R23000 愛知県 7495000 7228000 948000 4628000 1920000 231369 53918 1.41 ... 3718 17842 6159 16003 1558 148009 319344 79757 22575 30894
27 R27000 大阪府 8782000 8524000 1002000 5349000 2432000 208681 59780 1.27 ... 5442 26431 8184 27297 1573 172365 265161 80890 18350 25978

6 rows × 92 columns

絞り込み条件は複数書くこともできる. 以下はAND条件の例.AND条件は&で繋ぐ.なお,1つ1つの条件は丸括弧で囲む.

# 総人口が700万人以上かつ都道府県名が「全国」でないレコードをすべて抽出
df[(df.総人口 >= 7000000) & (df.都道府県 != '全国')]

# queryメソッドを使うと,以下のように書ける
df.query('総人口 >= 7000000 & 都道府県 != "全国"')
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
11 R11000 埼玉県 7337000 7136000 847000 4483000 2007000 161439 45424 1.22 ... 3550 13604 5575 16370 1474 123008 324793 87922 21820 35050
13 R13000 東京都 14038000 13443000 1535000 9301000 3202000 483372 95404 1.08 ... 10678 48072 17245 52842 3523 290125 321633 87973 29988 33099
14 R14000 神奈川県 9232000 8991000 1053000 5797000 2383000 195535 58836 1.22 ... 4984 21377 7605 23872 2012 165014 301379 85076 23065 29394
23 R23000 愛知県 7495000 7228000 948000 4628000 1920000 231369 53918 1.41 ... 3718 17842 6159 16003 1558 148009 319344 79757 22575 30894
27 R27000 大阪府 8782000 8524000 1002000 5349000 2432000 208681 59780 1.27 ... 5442 26431 8184 27297 1573 172365 265161 80890 18350 25978

5 rows × 92 columns

以下はOR条件の例.OR条件は|(パイプ)で繋ぐ. 条件が増えると,中括弧で条件を指定する方法は見にくくなる. 条件が多い場合は,queryメソッドを使った方がコードの可読性を高められる.

# 合計特殊出生率が1.8以上もしくは1.1未満のレコードをすべて抽出
df[(df.合計特殊出生率 >= 1.8) | (df.合計特殊出生率 < 1.1)]

# queryメソッドを使うと,以下のように書ける
# df.query('合計特殊出生率 >= 1.8 | 合計特殊出生率 < 1.1')
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
13 R13000 東京都 14038000 13443000 1535000 9301000 3202000 483372 95404 1.08 ... 10678 48072 17245 52842 3523 290125 321633 87973 29988 33099
47 R47000 沖縄県 1468000 1446000 240000 884000 344000 18157 14535 1.80 ... 607 3887 885 2432 614 57108 251735 68318 25189 18429

2 rows × 92 columns

16.5.6. 比較演算子を用いない絞り込み#

大抵の絞り込みの場合,Python固有の比較演算子を用いれば事足りるが,文字列データなど非数値データの絞り込み時にはpandas特有の絞り込み方法が必要となるケースがある. 以下はその例.

16.5.6.1. いずれかの値にマッチするレコードの取得#

「都道府県名が東京都,大阪府,愛知県のいずれかである」のように,ある列項目の値が指定したリストに含まれているかを条件にしたい場合はisinメソッドを用いる. 以下は,データフレームdfから都道府県名が東京都,大阪府,愛知県のいずれかであるレコードを抽出するコード例である.

target_prefectures = ['東京都', '大阪府', '愛知県']
df[df.都道府県.isin(target_prefectures)]

# 以下,queryメソッドを使った場合.外部変数には@を付ける
# df.query('都道府県 in @target_prefectures')
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
13 R13000 東京都 14038000 13443000 1535000 9301000 3202000 483372 95404 1.08 ... 10678 48072 17245 52842 3523 290125 321633 87973 29988 33099
23 R23000 愛知県 7495000 7228000 948000 4628000 1920000 231369 53918 1.41 ... 3718 17842 6159 16003 1558 148009 319344 79757 22575 30894
27 R27000 大阪府 8782000 8524000 1002000 5349000 2432000 208681 59780 1.27 ... 5442 26431 8184 27297 1573 172365 265161 80890 18350 25978

3 rows × 92 columns

16.5.6.2. 文字列の部分一致#

ある列の文字列が「特定の文字列を含む」といった部分一致条件を指定してレコードの抽出を行いたい場合がある. このようなケースでは以下に挙げたstr.xxxメソッドを使用する(strを忘れないように!).

  • ある列の値が特定の文字列を「含む」レコードを抽出したい場合: str.containsメソッド

  • ある列の値が特定の文字列から「始まる」レコードを抽出したい場合: str.startswithメソッド

  • ある列の値が特定の文字列から「終わる」レコードを抽出したい場合: str.endswithメソッド

以下はデータフレームdfから都道府県名が「府」で終わるレコードを抽出するコード例である.

# str.をつけ忘れるとエラーを吐く
df[df.都道府県.str.endswith('府')]

# 以下,queryメソッドを使った場合.
# df.query('都道府県.str.endswith("府")')
地域コード 都道府県 総人口 日本人人口 15歳未満人口 15〜64歳人口 65歳以上人口 外国人人口 出生数 合計特殊出生率 ... 歯科診療所数 医師数 歯科医師数 薬剤師数 保育所等数 保育所等在所児数 消費支出(二人以上の世帯) 食料費(二人以上の世帯) 住居費(二人以上の世帯) 教養娯楽費(二人以上の世帯)
26 R26000 京都府 2550000 2485000 282000 1512000 755000 52442 15818 1.22 ... 1286 9156 1973 6828 512 55412 299924 84056 22849 27799
27 R27000 大阪府 8782000 8524000 1002000 5349000 2432000 208681 59780 1.27 ... 5442 26431 8184 27297 1573 172365 265161 80890 18350 25978

2 rows × 92 columns

16.6. データフレームの保存#

データフレームの中身をCSV/TSVファイルに保存するにはDataFrameオブジェクトのto_csvメソッドを用いる. 以下,データフレームdfdatasetディレクトリにCSVファイルとして保存するコード例.

df.to_csv('dataset/SSDSE-E-2024.csv')

to_csvメソッドは指定がなければ,データフレームをCSVファイルとして保存する. TSVファイルとして保存したい場合は,以下のように引数sep\tを指定する. ファイル名の拡張子も.tsvとしておこう.

df.to_csv('dataset/SSDSE-E-2024.tsv', sep='\t')

見出しやインデックス(データフレームが割り振った索引名)を保存するかはheader引数およびindex引数で指定する. 双方の引数ともにデフォルトはTrue(保存する).

# 以下の場合,見出しはCSVファイルに保存し,インデックスは保存しない
df.to_csv('dataset/SSDSE-E-2024.csv', header=True, index=False)

16.7. 前処理#

pokemonDataは,ポケットモンスターシリーズに登場するポケモンの能力値をまとめたデータセットである. このデータセット中には,欠損値をもつポケモンデータがいくつかある. 以降,前処理の説明ではこのデータセットを用いる.

まずは,以下のコードでデータセットをpokemon_df変数に読み込んでおく.

# na_values引数に' 'を与えて,空文字を欠損値として認識させる
pokemon_df = pd.read_table(
    'https://raw.githubusercontent.com/lgreski/pokemonData/refs/heads/master/Pokemon.csv', sep=',', na_values=[' '])
pokemon_df
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation
0 1 Bulbasaur NaN Grass Poison 318 45 49 49 65 65 45 1
1 2 Ivysaur NaN Grass Poison 405 60 62 63 80 80 60 1
2 3 Venusaur NaN Grass Poison 525 80 82 83 100 100 80 1
3 4 Charmander NaN Fire NaN 309 39 52 43 60 50 65 1
4 5 Charmeleon NaN Fire NaN 405 58 64 58 80 65 80 1
... ... ... ... ... ... ... ... ... ... ... ... ... ...
1210 1023 Iron Crown NaN Steel Psychic 590 90 72 100 122 108 98 9
1211 1024 Terapagos Normal Form Normal NaN 450 90 65 85 65 85 60 9
1212 1024 Terapagos Terastal Form Normal NaN 600 95 95 110 105 110 85 9
1213 1024 Terapagos Stellar Form Normal NaN 700 160 105 110 130 110 85 9
1214 1025 Pecharunt NaN Poison Ghost 600 88 88 160 88 88 88 9

1215 rows × 13 columns

Warning

このノートで取り上げない前処理

このノートでは以下の項目について触れないが,重要な前処理なので興味があれば調べること.

  • 欠損値の穴埋め

  • ウィンドウ関数

  • データ拡張(オーバーサンプリングなど)

  • 日時処理

  • 自然言語処理(embeddingなど)

16.7.1. 列名の変更#

列名の変更はDataFrameオブジェクトのrenameメソッドで行う. renameメソッドののcolumns引数に{'変更前の列名': '変更後の列名'}の辞書を与えると,列名を変更できる. renameメソッドは列名変更後のDataFrameオブジェクトを返す.

pokemon_df.rename(columns={'Sp. Atk': 'Special_Attack', 'Sp. Def': 'Special_Defense'})
ID Name Form Type1 Type2 Total HP Attack Defense Special_Attack Special_Defense Speed Generation
0 1 Bulbasaur NaN Grass Poison 318 45 49 49 65 65 45 1
1 2 Ivysaur NaN Grass Poison 405 60 62 63 80 80 60 1
2 3 Venusaur NaN Grass Poison 525 80 82 83 100 100 80 1
3 4 Charmander NaN Fire NaN 309 39 52 43 60 50 65 1
4 5 Charmeleon NaN Fire NaN 405 58 64 58 80 65 80 1
... ... ... ... ... ... ... ... ... ... ... ... ... ...
1210 1023 Iron Crown NaN Steel Psychic 590 90 72 100 122 108 98 9
1211 1024 Terapagos Normal Form Normal NaN 450 90 65 85 65 85 60 9
1212 1024 Terapagos Terastal Form Normal NaN 600 95 95 110 105 110 85 9
1213 1024 Terapagos Stellar Form Normal NaN 700 160 105 110 130 110 85 9
1214 1025 Pecharunt NaN Poison Ghost 600 88 88 160 88 88 88 9

1215 rows × 13 columns

16.7.2. データフレームの結合#

16.7.2.1. 縦方向の結合#

同じ構造を持つ(列項目が同じ)2つのデータフレームを縦方向に結合するにはconcat関数を用いる. 縦方向に結合するconcat関数は,SQLのUNIONに相当する.

以下は,仮にデータフレームpokemon_df

  • 第4世代以下のポケモン情報を格納したpokemon_df1

  • 第5世代以上のポケモン情報を格納したpokemon_df2

の2つのデータフレームに分割されているとしたときに,2つのデータフレームを結合して第1世代から第9世代のポケモン情報をまとめた1つのデータフレームを作るコード例である.

# pokemon_dfが2つのデータフレームに分かれているとする
pokemon_df1 = pokemon_df[pokemon_df.Generation < 5]
pokemon_df2 = pokemon_df[pokemon_df.Generation >= 5]

# concat関数で2つのデータフレームを縦方向に結合する
pd.concat([pokemon_df1, pokemon_df2])
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation
0 1 Bulbasaur NaN Grass Poison 318 45 49 49 65 65 45 1
1 2 Ivysaur NaN Grass Poison 405 60 62 63 80 80 60 1
2 3 Venusaur NaN Grass Poison 525 80 82 83 100 100 80 1
3 4 Charmander NaN Fire NaN 309 39 52 43 60 50 65 1
4 5 Charmeleon NaN Fire NaN 405 58 64 58 80 65 80 1
... ... ... ... ... ... ... ... ... ... ... ... ... ...
1210 1023 Iron Crown NaN Steel Psychic 590 90 72 100 122 108 98 9
1211 1024 Terapagos Normal Form Normal NaN 450 90 65 85 65 85 60 9
1212 1024 Terapagos Terastal Form Normal NaN 600 95 95 110 105 110 85 9
1213 1024 Terapagos Stellar Form Normal NaN 700 160 105 110 130 110 85 9
1214 1025 Pecharunt NaN Poison Ghost 600 88 88 160 88 88 88 9

1215 rows × 13 columns

16.7.2.2. 横方向の結合#

SQLのJOINに相当する結合をPandasのデータフレームに対して行うにはmerge関数を用いる. merge関数の第1引数には結合したい1つ目のデータフレーム,第2引数には結合したい2つ目のデータフレームを指定する. また,on引数には結合に用いる列項目名を,how引数にはinner(内部結合),outer(外部結合),left(左外部結合),right(右外部結合)を指定できる.

以下は,仮にデータフレームpokemon_df

  • pokemon_df_base: IDNameGenerationFormType1Type2といったポケモンの基礎情報を格納

  • pokemon_df_status: IDFormTotalAttackDefenseSp. AtkSp. DefSpeedといったポケモンの戦闘力情報を格納

の2つのデータフレームに分割されているとしたときに,2つのデータフレームを横方向に結合してポケモン情報をまとめた1つのデータフレームを作るコード例である.

# pokemon_dfが2つのデータフレームに分かれているとする
pokemon_df_base = pokemon_df[['ID', 'Name', 'Generation', 'Form', 'Type1', 'Type2']]
pokemon_df_status = pokemon_df[['ID', 'Total', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed']]

# merge関数で2つのデータフレームを横方向に内部結合する(結合軸はID)
pd.merge(pokemon_df_base, pokemon_df_status, on=['ID'], how='inner')

# 実は下のようにも書ける
# pokemon_df_base.merge(pokemon_df_status, on=['ID'], how='inner')
ID Name Generation Form Type1 Type2 Total Attack Defense Sp. Atk Sp. Def Speed
0 1 Bulbasaur 1 NaN Grass Poison 318 49 49 65 65 45
1 2 Ivysaur 1 NaN Grass Poison 405 62 63 80 80 60
2 3 Venusaur 1 NaN Grass Poison 525 82 83 100 100 80
3 3 Venusaur 1 NaN Grass Poison 625 100 123 122 120 80
4 4 Charmander 1 NaN Fire NaN 309 52 43 60 50 65
... ... ... ... ... ... ... ... ... ... ... ... ...
1696 1024 Terapagos 9 Terastal Form Normal NaN 700 105 110 130 110 85
1697 1024 Terapagos 9 Stellar Form Normal NaN 450 65 85 65 85 60
1698 1024 Terapagos 9 Stellar Form Normal NaN 600 95 110 105 110 85
1699 1024 Terapagos 9 Stellar Form Normal NaN 700 105 110 130 110 85
1700 1025 Pecharunt 9 NaN Poison Ghost 600 88 160 88 88 88

1701 rows × 12 columns

16.7.3. データの整然化:横持ちデータから縦持ちデータへ#

以下の表は,Gapminderで公開されている世界各国の幸福度スコアをまとめたものである. 表中の各行は国を表し,年ごとの幸福度スコアが各列に格納されている. なお,国によっては幸福度スコアが計測されていない年もある.

以下の表は,上の表と同じ内容を別の表現で表したものである. 各行が1回の(幸福度スコア)計測に対応しており,行にはどの国(country)のいつ計測された(year)幸福度スコアがいくつだったか(happiness_score)の情報が格納されている. 下の表のように,

  • 個々の変数が列に対応し,

  • 個々の観測が行に対応し,

  • 個々の値がセルに対応し

  • 個々の観測ユニットの類型が1つの表に対応する

表データを整然データ(tidy data) あるいは縦持ちデータと呼ぶ. 整然データでない表データは雑然データ(messy data)横持ちデータと呼ばれる.

country year happiness_score
0 Afghanistan 2005 NaN
1 Angola 2005 NaN
2 Albania 2005 NaN
3 UAE 2005 NaN
4 Argentina 2005 NaN
... ... ... ...
3111 Vietnam 2023 63.3
3112 Yemen 2023 35.3
3113 South Africa 2023 50.8
3114 Zambia 2023 36.9
3115 Zimbabwe 2023 35.7

3116 rows × 3 columns

整然データはデータの管理がしやすい. また,計算機によるデータ処理を行う際には,整然データのほうが都合が良いことが多い. 例えば,例に挙げた幸福度スコアの表を

  • 雑然データ形式で格納したデータフレームmessy_df

  • 整然データ形式で格納したデータフレームtidy_df

のそれぞれが手元にあるとしよう. それぞれのデータフレームを使って,

  • ある年に計測した幸福度が50を超えている国を抽出したい

  • 国ごとに幸福度の平均値を算出したい

とき,整然データ形式のtidy_dfであれば

# ある年に計測した幸福度が50を超えている国を抽出
tidy_df[tidy_df.happiness_score >= 50]['country']

# 国毎に幸福度の平均値を算出
tidy_df.groupby('country')['happiness_score'].mean()

と書ける. 一方,雑然データ形式のmessy_dfを使った場合,アッサリとコードを書けない.

雑然データ形式のDataFrameオブジェクトを整然データ形式に変換するには,pandasのmelt関数を用いる. 例えば,上の例で用いた世界各国の幸福度スコアを格納した雑然データを整然データに変換するには,以下のようなコードを書く. melt関数の

  • 第1引数には雑然データ形式のDataFrameオブジェクト

  • id_vars引数にはIDとして使用する列名

  • var_name引数にはid_varsで指定しなかった(雑然データ中で)残りの列名をまとめる変数名

  • value_name引数には雑然データにおけるセル値に割り当てる変数名

を指定する.

# Gapminderで公開されている世界各国の幸福度スコアを格納したCSVファイルを読み込む
# このCSVファイルに格納された表データは雑然データ
happiness_df = pd.read_table('dataset/happiness_score.csv', sep=',')

# 整然データ形式に変換
tidy_df = pd.melt(happiness_df, id_vars=['country'], var_name='year', value_name='happiness_score')

雑然データは計算機では処理しづらいが,人間には理解しやすいというメリットがある. 整然データ形式のDataFrameオブジェクトを雑然データ形式に変換するには,DataFrameオブジェクトのpivotメソッドを用いる.

以下は,整然データ形式で格納された幸福度スコアのデータフレームtidy_dfを雑然データに変換するコード例である.

tidy_df.pivot(index='country', columns='year', values='happiness_score')
year 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023
country
Afghanistan NaN NaN NaN 37.2 44.0 47.6 38.3 37.8 35.7 31.3 39.8 42.2 26.6 26.9 23.8 NaN 24.4 12.8 14.5
Albania NaN NaN 46.3 NaN 54.9 52.7 58.7 55.1 45.5 48.1 46.1 45.1 46.4 50.0 50.0 53.6 52.5 52.1 54.5
Algeria NaN NaN NaN NaN NaN 54.6 53.2 56.0 NaN 63.5 NaN 53.4 52.5 50.4 47.5 54.4 52.2 55.4 NaN
Angola NaN NaN NaN NaN NaN NaN 55.9 43.6 39.4 38.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN
Argentina NaN 63.1 60.7 59.6 64.2 64.4 67.8 64.7 65.8 66.7 67.0 64.3 60.4 57.9 60.9 59.0 59.1 62.6 63.9
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Venezuela 71.7 65.3 NaN 62.6 71.9 74.8 65.8 70.7 65.5 61.4 55.7 40.4 50.7 50.1 50.8 45.7 51.1 59.5 57.6
Vietnam NaN 52.9 54.2 54.8 53.0 53.0 57.7 55.4 50.2 50.9 50.8 50.6 51.8 53.0 54.7 54.6 55.4 62.7 63.3
Yemen NaN NaN 44.8 NaN 48.1 43.5 37.5 40.6 42.2 39.7 29.8 38.3 32.5 30.6 42.0 NaN NaN 35.9 35.3
Zambia NaN 48.2 40.0 47.3 52.6 NaN 50.0 50.1 52.4 43.5 48.4 43.5 39.3 40.4 33.1 48.4 30.8 37.3 36.9
Zimbabwe NaN 38.3 32.8 31.7 40.6 46.8 48.5 49.5 46.9 41.8 37.0 37.4 36.4 36.2 26.9 31.6 31.6 33.0 35.7

164 rows × 19 columns

16.7.4. 新しい列の追加#

DataFrameオブジェクトに新しい列を追加する最も単純な方法は,中括弧で新規で追加する列に「アクセス」して値を代入する方法である. 以下は,データフレームpokemon_dfSpecial_Totalという列名を追加して,Sp. Atk列とSp. Def列の合計値を代入するコード例である.

pokemon_df['Special_Total'] = pokemon_df['Sp. Atk'] + pokemon_df['Sp. Def']

# 以下のように,ドットを使って代入先を指定する方法を用いると,データフレーム中に列名が存在しないと警告が出る
# pokemon_df.Special_Total = pokemon_df['Sp. Atk'] + pokemon_df['Sp. Def']

DataFrameオブジェクトに新しい列を追加するもう1つの方法はassignメソッドを用いる方法である. assignメソッドを用いると,複数の列を同時に追加できる.

以下は,データフレームpokemon_df

  • Sp. Atk列とSp. Def列の合計値を格納したSpecial_Totalという列

  • Attack列とDefense列の合計値を格納したBase_Totalという列

を追加するコード例である. なお,assignメソッドは返り値として列を追加後のDataFrameオブジェクトを返すことに注意(元のDataFrameオブジェクトを変更しない). また,assignメソッドを使用するときは,代入先の列名はクオーツ(’)で包まない(文字列リテラルにする)ことに注意.

pokemon_df = pokemon_df.assign(
    Special_Total = pokemon_df['Sp. Atk'] + pokemon_df['Sp. Def'],
    Base_Total = pokemon_df['Attack'] + pokemon_df['Defense']
)

16.7.5. 関数の適用によるデータ加工#

データフレーム中の任意の列の各データに一括処理を行うにはapplyメソッドを用いる. applyメソッドの第1引数には関数オブジェクトを与える. 必要ならargs引数に関数オブジェクトに渡す追加引数をリストで指定する.

以下は,データフレームpokemon_dfSpeedの値がその平均値よりも大きい場合は”fast”グループ,小さければ”slow”グループに割り当て,割り当てられたグループ名をspeed_class列に格納するコード例である.

# Speedの平均値をあらかじめ計算しておく
avg_speed = pokemon_df.Speed.mean()

def greater_than(val, avg_total):
    if val >= avg_speed:
        return 'fast'
    else:
        return 'slow'

# args引数にgreater_than関数に渡す追加引数avg_speedを指定
pokemon_df['speed_class'] = pokemon_df.Speed.apply(greater_than, args=[avg_speed])

applyメソッドはlambda関数を使うとすっきり書ける. 上記コード例をlambda関数を使って書いたのが以下である.

pokemon_df['speed_class'] = pokemon_df.Speed.apply(lambda s: 'fast' if s >= pokemon_df.Speed.mean() else 'slow')

# データの読み込み,複数の前処理を一気にやるなら,assignメソッドを使った方が読みやすい
# pokemon_df = pd.read_table(
#     'https://raw.githubusercontent.com/lgreski/pokemonData/refs/heads/master/Pokemon.csv',
#     sep=',', na_values=[' ']
# ).assign(
#     speed_class = lambda df: df.Speed.apply(lambda s: 'fast' if s >= pokemon_df.Speed.mean() else 'slow'),
#     attack_class = lambda df: df.Attack.apply(lambda s: 'high_attack' if s >= pokemon_df.Attack.mean() else 'low_attack')
# )

16.7.6. ダミー変数化#

商品カテゴリや性別など,データには何らかのカテゴリやラベルを示す値が数値または文字列で入っていることがしばしばある. このようなカテゴリデータ(categorical data) は,そのまま扱うとに機械学習では都合が悪い. そのため,ダミー変数化と呼ばれるデータ変換がしばしば行われる.

代表的なダミー変数化方法はone-hotエンコーディングである. One-hotエンコーディングは,カテゴリデータが格納されているある列の各値が特定のカテゴリ値と一致しているかを0か1の2値で表す新たな列データを生成する. 例えば,データフレーム中に性別情報を示すgenderという列があり,gender列には「男性」「女性」「答えたくない」の3つのカテゴリ値が格納されているとしよう. このgender列のデータにone-hotエンコーディングを適用すると,gender列と同等の情報を表現するために,

  • is_gender_male列: 対象レコードの性別情報が「男性」であるか否かを1もしくは0で示す

  • is_gender_female列: 対象レコードの性別情報が「女性」であるか否かを1もしくは0で示す

  • is_gender_NA列: 対象レコードの性別情報が「答えたくない」であるか否かを1もしくは0で示す

のような3列を生成する.

実際にone-hotエンコーディングを用いる際には,すべてのカテゴリ値を列に変換しない. 例えば,上のgender列の例の場合,gender列が取り得る3つの値をすべて表現するにはis_gender_male列,is_gender_female列,is_gender_NA列からいずれか2つの列を用いれば十分である.

pandasには,データフレーム中の特定の列にone-hotエンコーディングを適用するpandas.get_dummies関数がある. get_dummies関数は,第1引数にDataFrameオブジェクト,columns引数にダミー変数化の対象となる列項目のリストを指定する. また,drop_first引数にTrueを与えると,(上のgender列の例でダミー変数化された列が2つで十分だったように)カテゴリ値を表現するのに余分なダミー変数列を生成しない.

以下は,データフレームpokemon_dfGeneration列にone-hotエンコーディングを適用するコード例である. Generation列の値は1から9の9種類あるが,drop_first引数にTrueを指定しているので,get_dummies関数によって生成されたダミー変数列は8つとなっている.

# get_dummies関数の返値は,ダミー変数化した列を含めた新しいDataFrameオブジェクト
pd.get_dummies(pokemon_df, columns=['Generation'], drop_first=True)
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk ... Base_Total speed_class Generation_2 Generation_3 Generation_4 Generation_5 Generation_6 Generation_7 Generation_8 Generation_9
0 1 Bulbasaur NaN Grass Poison 318 45 49 49 65 ... 98 slow False False False False False False False False
1 2 Ivysaur NaN Grass Poison 405 60 62 63 80 ... 125 slow False False False False False False False False
2 3 Venusaur NaN Grass Poison 525 80 82 83 100 ... 165 fast False False False False False False False False
3 4 Charmander NaN Fire NaN 309 39 52 43 60 ... 95 slow False False False False False False False False
4 5 Charmeleon NaN Fire NaN 405 58 64 58 80 ... 122 fast False False False False False False False False
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1210 1023 Iron Crown NaN Steel Psychic 590 90 72 100 122 ... 172 fast False False False False False False False True
1211 1024 Terapagos Normal Form Normal NaN 450 90 65 85 65 ... 150 slow False False False False False False False True
1212 1024 Terapagos Terastal Form Normal NaN 600 95 95 110 105 ... 205 fast False False False False False False False True
1213 1024 Terapagos Stellar Form Normal NaN 700 160 105 110 130 ... 215 fast False False False False False False False True
1214 1025 Pecharunt NaN Poison Ghost 600 88 88 160 88 ... 248 fast False False False False False False False True

1215 rows × 23 columns

16.7.7. 集約演算#

表データを扱うpandasは,SQLのように集約演算を行うことができる. pandasにおける集約演算は,DataFrameオブジェクトのgroupbyメソッドで行う. groupsメソッドは引数で指定された値でグループ化したGroupByオブジェクトを返すので,GroupByオブジェクトの関心のある列項目にアクセスして集計関数を適用する.

以下は,データフレームpokemon_dfGeneration項目の値で集約し,GeneartionごとのHPAttackDefense項目の平均値を計算するコードである.

# 平均値
pokemon_df.groupby(['Generation'])[['HP', 'Attack', 'Defense']].mean()

# 個数
# pokemon_df.groupby(['Generation'])[['HP', 'Attack', 'Defense']].size()

# 合計
# pokemon_df.groupby(['Generation'])[['HP', 'Attack', 'Defense']].sum()

# 最大値
# pokemon_df.groupby(['Generation'])[['HP', 'Attack', 'Defense']].max()

# 最小値
# pokemon_df.groupby(['Generation'])[['HP', 'Attack', 'Defense']].min()

# 中央値
# pokemon_df.groupby(['Generation'])[['HP', 'Attack', 'Defense']].median()

# 標準偏差
# pokemon_df.groupby(['Generation'])[['HP', 'Attack', 'Defense']].std()
HP Attack Defense
Generation
1 64.211921 72.913907 68.225166
2 70.980000 68.260000 69.690000
3 65.425532 73.936170 69.475177
4 72.220339 79.127119 76.584746
5 71.709091 82.442424 72.078788
6 72.549618 94.923664 87.877863
7 70.434426 86.508197 78.278689
8 75.346939 86.197279 76.829932
9 78.685714 83.850000 77.007143

なお,集約されたレコードの個数を求めるだけなら,groupbyメソッドを使わず,下記のようにvalue_countsメソッドを使った方が簡単に書ける.

# ジェネレーションごとのレコードの個数を計算
pokemon_df['Generation'].value_counts()
Generation
5    165
1    151
8    147
3    141
9    140
6    131
7    122
4    118
2    100
Name: count, dtype: int64

平均と標準偏差を同時に知りたいなど,複数の集計関数を同時に適用したい場合,DataFrameオブジェクトのaggメソッドを用いる. aggメソッドの引数には集計対象となる列項目をキー,集計のための関数(のリスト)を値とする辞書を指定する.

以下は,データフレームpokemon_dfGeneration値で集約し,

  • Total値の平均,標準偏差

  • Attack値の最大値,最小値

  • Defense値の分散

を計算するコード例である. なお,下記コード例のように,関数は自作関数も指定可能である.

# numpyの関数を使うために,ライブラリを読み込んでおく
import numpy as np

# 標準偏差から分散を求める関数を定義
def variance(s: pd.Series) -> float:
    return s.std() ** 2

pokemon_df.groupby(['Generation']).agg({
    'Total': [np.mean, np.std], # 平均と標準偏差を求める関数をリストで指定
    'Attack': [np.max, np.min], # 最大値と最小値を求める関数をリストで指定
    'Defense': variance         # varianceは自分で定義した関数
})
Total Attack Defense
mean std max min variance
Generation
1 407.642384 99.875212 134 5 724.508962
2 407.180000 112.456266 134 10 1241.226162
3 408.248227 116.596260 180 15 1002.579737
4 447.898305 119.190101 165 5 918.911560
5 434.896970 107.931190 170 25 518.585218
6 505.610687 135.629335 190 22 1338.769583
7 452.893443 122.155761 181 20 1017.194418
8 460.795918 128.727222 165 20 963.101016
9 462.157143 115.871193 160 30 756.669013

16.7.8. 欠損値の発見#

データフレーム中にある欠損値を発見するには,DataFrameオブジェクトのisnullメソッドを用いる. DataFrameオブジェクトにisnullメソッドを適用すると,データフレーム中の各セルが欠損値か否かを示す真偽値を返す.

pokemon_df.isnull()
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation Special_Total Base_Total speed_class
0 False False True False False False False False False False False False False False False False
1 False False True False False False False False False False False False False False False False
2 False False True False False False False False False False False False False False False False
3 False False True False True False False False False False False False False False False False
4 False False True False True False False False False False False False False False False False
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1210 False False True False False False False False False False False False False False False False
1211 False False False False True False False False False False False False False False False False
1212 False False False False True False False False False False False False False False False False
1213 False False False False True False False False False False False False False False False False
1214 False False True False False False False False False False False False False False False False

1215 rows × 16 columns

欠損値を扱う場合,実際に知りたいのは

  1. どの「項目(列)」に欠損値が存在するのか?

  2. どの「レコード(行)」が欠損値を持つのか?

であることが大半であろう.そのため,上記ニーズに応えるにはisnullメソッドが返す結果を列方向か行方向に集約する必要がある.

16.7.8.1. 欠損値が存在する項目(列)を知りたい場合#

DataFrameオブジェクトのanyメソッドは,列方向(or 行方向)にセルの要素を調べたとき1つでもTrueがあればTrueを,それ以外ならFalseを返す,という操作を列(or 行)ごとに行う. 列方向に欠損値の有無を調べたい場合は,以下のコード例のようにanyメソッドの引数axis0を設定する.

pokemon_df.isnull().any(axis=0)
ID               False
Name             False
Form              True
Type1            False
Type2             True
                 ...  
Speed            False
Generation       False
Special_Total    False
Base_Total       False
speed_class      False
Length: 16, dtype: bool

欠損値がある列を知りたい場合は,以下のようにする.

pokemon_df.columns[pokemon_df.isnull().any(axis=0)]
Index(['Form', 'Type2'], dtype='object')

16.7.8.2. 欠損値が存在するレコード(行)を知りたい場合#

行方向に欠損値の有無を調べたい場合は,以下のコード例のようにanyメソッドの引数axis1を設定する.

pokemon_df.isnull().any(axis=1)
0       True
1       True
2       True
3       True
4       True
        ... 
1210    True
1211    True
1212    True
1213    True
1214    True
Length: 1215, dtype: bool

isnull().any(axis=1)は各レコード(行)に欠損値が含まれるか否かの情報のみを返すので,欠損値が含まれるレコードを抽出するには以下のようにする.

pokemon_df[pokemon_df.isnull().any(axis=1)]
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation Special_Total Base_Total speed_class
0 1 Bulbasaur NaN Grass Poison 318 45 49 49 65 65 45 1 130 98 slow
1 2 Ivysaur NaN Grass Poison 405 60 62 63 80 80 60 1 160 125 slow
2 3 Venusaur NaN Grass Poison 525 80 82 83 100 100 80 1 200 165 fast
3 4 Charmander NaN Fire NaN 309 39 52 43 60 50 65 1 110 95 slow
4 5 Charmeleon NaN Fire NaN 405 58 64 58 80 65 80 1 145 122 fast
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1210 1023 Iron Crown NaN Steel Psychic 590 90 72 100 122 108 98 9 230 172 fast
1211 1024 Terapagos Normal Form Normal NaN 450 90 65 85 65 85 60 9 150 150 slow
1212 1024 Terapagos Terastal Form Normal NaN 600 95 95 110 105 110 85 9 215 205 fast
1213 1024 Terapagos Stellar Form Normal NaN 700 160 105 110 130 110 85 9 240 215 fast
1214 1025 Pecharunt NaN Poison Ghost 600 88 88 160 88 88 88 9 176 248 fast

1053 rows × 16 columns

16.7.9. 欠損値を持つレコードの除去#

欠損値を含むレコードを除去する場合,以下の2パターンの処理が考えられる:

  1. いずれかの列項目に1つでも欠損値を含む場合,そのレコードを除去する

  2. 指定した列項目に欠損値を含む場合,そのレコードを除去する

いずれのパターンの場合も,DataFrameオブジェクトのdropnaメソッドを用いる. 違いはdropnaメソッドに与えるパラメータにある.

16.7.9.1. いずれかの列項目に1つでも欠損値を含む場合#

パターン1の場合,dropnaメソッドの引数howanyを指定する. 以下は,データフレームpokemon_dfからいずれかの列項目が空の値になっているレコードを全て除去した上で,新たなデータフレームを返すコード例である.

pokemon_df.dropna(how='any')
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation Special_Total Base_Total speed_class
420 413 Wormadam Plant Cloak Bug Grass 424 60 59 85 79 105 36 4 184 144 slow
421 413 Wormadam Sandy Cloak Bug Ground 424 60 79 105 59 85 36 4 144 184 slow
422 413 Wormadam Trash Cloak Bug Steel 424 60 69 95 69 95 36 4 164 164 slow
489 479 Rotom Heat Rotom Electric Fire 520 50 65 107 105 107 86 4 212 172 fast
490 479 Rotom Wash Rotom Electric Water 520 50 65 107 105 107 86 4 212 172 fast
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1159 978 Tatsugiri Droopy Form Dragon Water 475 68 50 60 120 95 82 9 215 110 fast
1160 978 Tatsugiri Stretchy Form Dragon Water 475 68 50 60 120 95 82 9 215 110 fast
1202 1017 Ogerpon Wellspring Mask Grass Water 550 80 120 84 60 96 110 9 156 204 fast
1203 1017 Ogerpon Hearthflame Mask Grass Fire 550 80 120 84 60 96 110 9 156 204 fast
1204 1017 Ogerpon Cornerstone Mask Grass Rock 550 80 120 84 60 96 110 9 156 204 fast

162 rows × 16 columns

16.7.9.2. 指定した列項目に欠損値を含む場合#

指定した列項目にのみ空値があるレコードを除外する場合には,dropnaメソッドのsubset引数に対象列名を指定する. 以下は,データフレームpokemon_dfからType2の項目に空値を含むレコードを除外し,新たなデータフレームを返すコード例である.

pokemon_df.dropna(subset='Type2')
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation Special_Total Base_Total speed_class
0 1 Bulbasaur NaN Grass Poison 318 45 49 49 65 65 45 1 130 98 slow
1 2 Ivysaur NaN Grass Poison 405 60 62 63 80 80 60 1 160 125 slow
2 3 Venusaur NaN Grass Poison 525 80 82 83 100 100 80 1 200 165 fast
5 6 Charizard NaN Fire Flying 534 78 84 78 109 85 100 1 194 162 fast
11 12 Butterfree NaN Bug Flying 395 60 45 50 90 80 70 1 170 95 slow
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1207 1020 Gouging Fire NaN Fire Dragon 590 105 115 121 65 93 91 9 158 236 fast
1208 1021 Raging Bolt NaN Electric Dragon 590 125 73 91 137 89 75 9 226 164 fast
1209 1022 Iron Boulder NaN Rock Psychic 590 90 120 80 68 108 124 9 176 200 fast
1210 1023 Iron Crown NaN Steel Psychic 590 90 72 100 122 108 98 9 230 172 fast
1214 1025 Pecharunt NaN Poison Ghost 600 88 88 160 88 88 88 9 176 248 fast

669 rows × 16 columns

subset引数では,対象列をリスト形式で複数指定することが可能である. 以下は,データフレームpokemon_dfからType2Formの項目の「両方」に空値を含むレコードを除外し,新たなデータフレームを返すコード例である.

# howに'any'を指定すると,Type2とForm項目の「いずれか」に欠損値があるレコードを除外する
pokemon_df.dropna(subset=['Type2', 'Form'], how='all')
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation Special_Total Base_Total speed_class
0 1 Bulbasaur NaN Grass Poison 318 45 49 49 65 65 45 1 130 98 slow
1 2 Ivysaur NaN Grass Poison 405 60 62 63 80 80 60 1 160 125 slow
2 3 Venusaur NaN Grass Poison 525 80 82 83 100 100 80 1 200 165 fast
5 6 Charizard NaN Fire Flying 534 78 84 78 109 85 100 1 194 162 fast
11 12 Butterfree NaN Bug Flying 395 60 45 50 90 80 70 1 170 95 slow
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1210 1023 Iron Crown NaN Steel Psychic 590 90 72 100 122 108 98 9 230 172 fast
1211 1024 Terapagos Normal Form Normal NaN 450 90 65 85 65 85 60 9 150 150 slow
1212 1024 Terapagos Terastal Form Normal NaN 600 95 95 110 105 110 85 9 215 205 fast
1213 1024 Terapagos Stellar Form Normal NaN 700 160 105 110 130 110 85 9 240 215 fast
1214 1025 Pecharunt NaN Poison Ghost 600 88 88 160 88 88 88 9 176 248 fast

737 rows × 16 columns

16.7.10. 外れ値の除去 by IQR#

外れ値の検出には様々な手法が提案されているが,四分位範囲(IQR; interquartile range) を用いた方法は堅牢な手法として知られている. pandasでIQRを用いて外れ値を検出・除外するには,

  1. Q1(第1四分位数),Q3(第3四分位数),IQRの計算

  2. IQRを考慮した外れ値の判定

  3. 外れ値の除去

の流れで処理を行う.

以下は,データフレームpokemon_dfのレコードについて,Total列の値に着目してIQRを用いた外れ値の除去をするコード例である.

# quantileメソッドを使って,Q1とQ3を計算
Q1 = pokemon_df['Total'].quantile(0.25)
Q3 = pokemon_df['Total'].quantile(0.75)
IQR = Q3 - Q1

# 外れ値の判定.Q1 − 1.5 IQRよりも小さい,あるいは Q3 + 1.5 IQRよりも大きい値が外れ値
is_outlier = (pokemon_df.Total >= Q1 - 1.5 * IQR) & (pokemon_df.Total <= Q3 + 1.5 * IQR)

# 外れ値を除外せず,外れ値か否かをデータフレームに記録しておきたい場合は
# pokemon_df['is_outlier'] = is_outlier

# 外れ値を除いたデータフレーム
pokemon_df[is_outlier]

# queryメソッドを使うと,外れ値の除去するためのコードは以下のように書ける
#pokemon_df.query('@Q1 - 1.5 * @IQR <= Total <= @Q3 + 1.5 * @IQR')
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation Special_Total Base_Total speed_class
0 1 Bulbasaur NaN Grass Poison 318 45 49 49 65 65 45 1 130 98 slow
1 2 Ivysaur NaN Grass Poison 405 60 62 63 80 80 60 1 160 125 slow
2 3 Venusaur NaN Grass Poison 525 80 82 83 100 100 80 1 200 165 fast
3 4 Charmander NaN Fire NaN 309 39 52 43 60 50 65 1 110 95 slow
4 5 Charmeleon NaN Fire NaN 405 58 64 58 80 65 80 1 145 122 fast
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1210 1023 Iron Crown NaN Steel Psychic 590 90 72 100 122 108 98 9 230 172 fast
1211 1024 Terapagos Normal Form Normal NaN 450 90 65 85 65 85 60 9 150 150 slow
1212 1024 Terapagos Terastal Form Normal NaN 600 95 95 110 105 110 85 9 215 205 fast
1213 1024 Terapagos Stellar Form Normal NaN 700 160 105 110 130 110 85 9 240 215 fast
1214 1025 Pecharunt NaN Poison Ghost 600 88 88 160 88 88 88 9 176 248 fast

1214 rows × 16 columns

16.7.11. ソート#

ある列項目の値の大きさでデータフレーム中のレコードを並び替えるには,DataFrameオブジェクトのsort_valuesメソッドを用いる. sort_valuesメソッドの第1引数にソート基準となる列項目名を指定する. また,ascending引数にTrueを指定すると昇順に,Falseを指定する降順にソートされる(デフォルト値はTrue).

以下は,データフレームpokemon_df中のレコードをTotalの大きい順(降順)で並べるコード例である.

pokemon_df.sort_values('Total', ascending=False)
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation Special_Total Base_Total speed_class
1054 890 Eternatus Eternamax Poison Dragon 1125 255 115 250 125 250 130 8 375 365 fast
688 150 Mewtwo Mega Mewtwo X Psychic Fighting 780 106 190 100 154 100 130 6 254 290 fast
689 150 Mewtwo Mega Mewtwo Y Psychic NaN 780 106 150 70 194 120 140 6 314 220 fast
717 384 Rayquaza Mega Rayquaza Dragon Flying 780 105 180 100 180 100 115 6 280 280 fast
716 383 Groudon Primal Groudon Ground Fire 770 100 180 160 150 90 90 6 240 340 fast
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
297 298 Azurill NaN Normal Fairy 190 50 20 40 20 40 20 3 60 60 slow
1030 872 Snom NaN Ice Bug 185 30 25 35 45 30 20 8 75 60 slow
190 191 Sunkern NaN Grass NaN 180 30 30 30 30 30 30 2 60 60 slow
981 824 Blipbug NaN Bug NaN 180 25 20 20 25 45 45 8 70 40 slow
859 746 Wishiwashi Solo Form Water NaN 175 45 20 20 25 25 40 7 50 40 slow

1215 rows × 16 columns

16.7.12. 順位付け#

ある列項目の値でレコードを順位付けをしたいケースには,rankメソッドを用いる. sort_valuesメソッドと同様に,rankメソッドはascending引数を持つ. ascending引数にTrueを指定すると昇順に,Falseを指定する降順にソートし順位付けを行う(デフォルト値はTrue).

以下は,データフレームpokemon_df中のTotalフィールドの値が大きい順にソートし,各レコードの順位を返すコード例である.

pokemon_df['Total'].rank(ascending=False)
0       962.0
1       782.5
2       292.0
3       998.5
4       782.5
        ...  
1210    127.0
1211    661.0
1212     95.5
1213     15.5
1214     95.5
Name: Total, Length: 1215, dtype: float64

同順位のレコードが複数ある場合の順位付け方法はrankメソッドのmethod引数で指定する. method引数で指定できる主なオプションは以下の通り(デフォルトはaverage).

  • min: 順位の最小値を返す(例: 1位,2位,2位,4位)

  • max: 順位の最大値を返す(例: 1位,3位,3位,4位)

  • average: 平均の順位を返す(例: 1位,2.5位,2.5位,4位)

以下は,データフレームpokemon_df中のTotalフィールドの値の降順で順位付け(同順位は順位の最小値で順位付け)するコード例である.

pokemon_df['Total'].rank(ascending=False, method='min')
0       961.0
1       770.0
2       282.0
3       997.0
4       770.0
        ...  
1210    122.0
1211    656.0
1212     71.0
1213     10.0
1214     71.0
Name: Total, Length: 1215, dtype: float64

順位の値をデータフレームに記憶したい場合は,以下のように新しい列を作って順位付けの結果を代入すればよい.

pokemon_df['total_rank'] = pokemon_df['Total'].rank(ascending=False, method='min')

# assignメソッドを使って,以下のようにも書ける
#pokemon_df.assign(
#    attack_rank = lambda df: df['Total'].rank(ascending=False, method='min')
#)

16.7.13. ビン分割#

ある項目の値に応じてデータフレーム中のレコードをいくつかのグループに分ける処理はビン分割(binning) と呼ばれる. pandasでビン分割を行うにはcut関数を用いる. cut関数の第1引数にはデータフレームの列データ(射影),bins引数には分割後のグループ数を指定する. cut関数は指定列の最小値と最大値の区間をbinsで指定されたグループ数で均等分割し,各列の値が分割されたどの区間の含まれるかを返す.

以下は,データフレームpokemon_dfTotal値を4つの区間に均等分割し,各レコードのTotal値がどの区間に含まれるかを返すコード例である.

pd.cut(pokemon_df.Total, bins=4)
0       (174.05, 412.5]
1       (174.05, 412.5]
2        (412.5, 650.0]
3       (174.05, 412.5]
4       (174.05, 412.5]
             ...       
1210     (412.5, 650.0]
1211     (412.5, 650.0]
1212     (412.5, 650.0]
1213     (650.0, 887.5]
1214     (412.5, 650.0]
Name: Total, Length: 1215, dtype: category
Categories (4, interval[float64, right]): [(174.05, 412.5] < (412.5, 650.0] < (650.0, 887.5] < (887.5, 1125.0]]

分割区間に名前を付けるにはlabels引数を使う. labelsにリストを与えると,cut関数はlabelsで指定されたラベルを使って結果を返す.

以下は上で用いたデータフレームpokemon_dfのビン分割において,各ビンにA,B,C,Dの名前を付けるコード例である.

  • \(174.05 < Total \leq 412.5\)ならD

  • \(412.5 < Total \leq 650\)ならC

  • \(650.0 < Total \leq 887.5\)ならB

  • \(887.5 < Total \leq 1125\)ならA

とラベルが振られる.

pd.cut(pokemon_df.Total, bins=4, labels=['D', 'C', 'B', 'A'])
0       D
1       D
2       C
3       D
4       D
       ..
1210    C
1211    C
1212    C
1213    B
1214    C
Name: Total, Length: 1215, dtype: category
Categories (4, object): ['D' < 'C' < 'B' < 'A']

cut関数のbins引数に数値のリストを与えると,分割区間の境界を明示的に与えることができる. 例えば,bins引数に数値リスト[a, b, c]を与えると,データを

  • aより大きくb以下

  • bより大きくc以下 の区間で分割する.

以下は,データフレームpokemon_dfTotalの値を

  • 第1四分位数(25%)以下

  • 第1四分位数より大きく第2四分位数以下(50%)

  • 第2四分位数より大きく第3四分位数以下(75%)

  • 第3四分位数より大きく最大値以下

で分割するコード例である.

bins=[
    # パーセンタイルの計算にはquantileメソッドを用いる
    pokemon_df.Total.quantile(0),
    pokemon_df.Total.quantile(0.25),
    pokemon_df.Total.quantile(0.50),
    pokemon_df.Total.quantile(0.75),
    pokemon_df.Total.quantile(1),
]

# ビン分割結果を`total_class`列に格納
# include_lowest引数をTrueにすると一番はじめの区間の左端の値を含むようにする(これがFalseだと最小値がどのビンにも含まれなくなる)
pokemon_df['total_class'] = pd.cut(pokemon_df.Total, bins=bins, labels=['D', 'C', 'B', 'A'], include_lowest=True)

16.7.14. NumPy行列の取得#

DataFrameオブジェクトからNumPy形式の(数値)行列を取り出すには,DataFrameオブジェクトのvaluesメソッドを用いる. 以下は,データフレームpokemon_dfからポケモンの戦闘力値情報(HPAttackDefenseSp. AtkSp. DefSpeed)を抜き出して,NumPy行列(ndarray)に変換するコード例である.

target_columns = ['HP', 'Attack', 'Defense', 'Sp. Atk', 'Sp. Def', 'Speed']
pokemon_df[target_columns].values
array([[ 45,  49,  49,  65,  65,  45],
       [ 60,  62,  63,  80,  80,  60],
       [ 80,  82,  83, 100, 100,  80],
       ...,
       [ 95,  95, 110, 105, 110,  85],
       [160, 105, 110, 130, 110,  85],
       [ 88,  88, 160,  88,  88,  88]])

16.7.15. 正規化・標準化#

データ集合中の各データを

  • 最大値が1,最小値が0になるように変換する正規化(normalization)

  • 平均値が0,分散が1になるように変換する標準化(standardization)

するためのPythonライブラリは色々ある.

pandasライブラリだけで完結させるには,愚直に正規化,標準化後の値を計算すればよい. データ集合\(X\)の最大値を\(X_{max}\),最小値を\(X_{min}\),平均を\(\mu_{X}\),標準偏差を\(\sigma_{X}\)とすると,データ\(x \in X\)の正規化後の値は

\[ \frac{x - X_{min}}{X_{max}-X_{min}} \]

となる.また,データ\(x \in X\)の標準化後の値は $\( \frac{x - \mu_{X}}{\sigma_{X}} \)$ となる. これを踏まえると,例えば,データフレームpokemon_dfTotalの値を正規化して,その値をnormalized_totalとして格納するコードは以下のように書ける.

pokemon_df['normalized_total'] = (pokemon_df.Total - pokemon_df.Total.min()) / (pokemon_df.Total.max() - pokemon_df.Total.min())

また,データフレームpokemon_dfTotalの値を標準化して,その値をstandardized_totalとして格納するコードは以下のように書ける.

pokemon_df['standardized_total'] = (pokemon_df.Total - pokemon_df.Total.mean()) / pokemon_df.Total.std()

代表的な方法はscikit-learnライブラリのpreprocessingパッケージを用いれば,複数の列項目を一括で正規化・標準化することができる. 正規化にはMinMaxScaler,標準化にはStandardScalerクラスを用いる. 両クラスともに変換対象はNumPy行列(numpy.ndarray)を対象とするので,この方法を用いる場合はまず変換対象となるデータを下記のようにNumPy行列化しておく.

# 'Total', 'HP', 'Attack', 'Defense'の4項目を行列として取り出す
X = pokemon_df[['Total', 'HP', 'Attack', 'Defense']].values

scikit-learnライブラリで正規化する場合は,下記のようにMinMaxScalerオブジェクトのfit_transformメソッドを用いる.

from sklearn.preprocessing import MinMaxScaler

minmax_scaler = MinMaxScaler()
X_scaled = minmax_scaler.fit_transform(X)

X_scaled
array([[0.15052632, 0.17322835, 0.23783784, 0.17959184],
       [0.24210526, 0.23228346, 0.30810811, 0.23673469],
       [0.36842105, 0.31102362, 0.41621622, 0.31836735],
       ...,
       [0.44736842, 0.37007874, 0.48648649, 0.42857143],
       [0.55263158, 0.62598425, 0.54054054, 0.42857143],
       [0.44736842, 0.34251969, 0.44864865, 0.63265306]])

scikit-learnライブラリで標準化する場合は,下記のようにStandardScalerオブジェクトのfit_transformメソッドを用いる.

from sklearn.preprocessing import StandardScaler

standard_scaler = StandardScaler()
X_scaled = standard_scaler.fit_transform(X)

標準化されたX_scaledの各列の平均,標準偏差を計算すると,どの列も平均値が0,標準偏差が1付近になっていることが分かる.

# 各列の平均値を計算
X_scaled.mean(axis=0)
array([ 1.40354121e-16,  1.52050297e-16, -9.35694138e-17, -1.87138828e-16])
# 各列の標準偏差を計算
X_scaled.std(axis=0)
array([1., 1., 1., 1.])

16.7.16. データの分割#

機械学習を行う際,手持ちのデータを訓練(学習)用データとテスト(評価)用データに分割することが一般的である. pandasで読み込んだデータフレームを機械学習用にデータ分割するには,以下の手順を踏む.

  1. データフレームから注目する説明変数(特徴量)と目的変数を取り出す

  2. (必要なら)データの前処理

  3. scikit-learnライブラリのtrain_test_split関数でデータを分割

以下は,タイタニック号の乗船客情報をtitanic_df変数に読み込み,

  • 沈没事故から生還したかを示す値を目的変数(Survived列)

  • それ以外の列情報を説明変数

として抽出し,訓練データとテストデータの割合が70:30になるようデータを分割するコード例である. train_test_split関数のtest_size引数を0.3とすることでテストデータの割合を30%になるようにし,shuffle引数をTrueにすることでデータをシャッフルしている. また,statify引数に目的変数であるyを指定することで,元のデータの分布(つまり生存した乗客とそうでない乗客の割合)を維持するように訓練データとテストデータに分割するようにしている(※この処理は層化と呼ばれる).

# scikit-learnライブラリのtrain_test_split関数を読み込む
from sklearn.model_selection import train_test_split

# データの読み込み
titanic_df = pd.read_table('https://raw.githubusercontent.com/hontolab-courses/dmml-2022/main/dataset/titanic_train.csv', sep=',')

# 説明変数と目的変数の抽出.
# dropメソッドは指定した列を除いたデータフレームを返す
X = titanic_df.drop('Survived', axis=1)
y = titanic_df['Survived']

# データ分割
# 訓練用に分割されたデータはX_train,y_train,テスト用に分割されたデータはX_test,y_test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, shuffle=True, stratify=y)