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]
と書くと,データフレームdf
のi
件目(ゼロを起点)から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万以上かそうでないかをTrue
かFalse
で表したpandas.Series(リストのようなもの)が返ってくる.
返ってくるpandas.Seriesでは,nつ目の要素がdf
のn番目のレコードがTrue
かFalse
を表している.
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
メソッドを用いる.
以下,データフレームdf
をdataset
ディレクトリに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
:ID
,Name
,Generation
,Form
,Type1
,Type2
といったポケモンの基礎情報を格納pokemon_df_status
:ID
,Form
,Total
,Attack
,Defense
,Sp. Atk
,Sp. Def
,Speed
といったポケモンの戦闘力情報を格納
の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_df
にSpecial_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_df
のSpeed
の値がその平均値よりも大きい場合は”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_df
のGeneration
列に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_df
をGeneration
項目の値で集約し,Geneartion
ごとのHP
,Attack
,Defense
項目の平均値を計算するコードである.
# 平均値
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_df
をGeneration
値で集約し,
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
欠損値を扱う場合,実際に知りたいのは
どの「項目(列)」に欠損値が存在するのか?
どの「レコード(行)」が欠損値を持つのか?
であることが大半であろう.そのため,上記ニーズに応えるにはisnull
メソッドが返す結果を列方向か行方向に集約する必要がある.
16.7.8.1. 欠損値が存在する項目(列)を知りたい場合#
DataFrameオブジェクトのany
メソッドは,列方向(or 行方向)にセルの要素を調べたとき1つでもTrueがあればTrueを,それ以外ならFalseを返す,という操作を列(or 行)ごとに行う.
列方向に欠損値の有無を調べたい場合は,以下のコード例のようにany
メソッドの引数axis
に0
を設定する.
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
メソッドの引数axis
に1
を設定する.
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つでも欠損値を含む場合,そのレコードを除去する
指定した列項目に欠損値を含む場合,そのレコードを除去する
いずれのパターンの場合も,DataFrameオブジェクトのdropna
メソッドを用いる.
違いはdropna
メソッドに与えるパラメータにある.
16.7.9.1. いずれかの列項目に1つでも欠損値を含む場合#
パターン1の場合,dropna
メソッドの引数how
にany
を指定する.
以下は,データフレーム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
からType2
とForm
の項目の「両方」に空値を含むレコードを除外し,新たなデータフレームを返すコード例である.
# 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を用いて外れ値を検出・除外するには,
Q1(第1四分位数),Q3(第3四分位数),IQRの計算
IQRを考慮した外れ値の判定
外れ値の除去
の流れで処理を行う.
以下は,データフレーム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_df
のTotal
値を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_df
のTotal
の値を
第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
からポケモンの戦闘力値情報(HP
,Attack
,Defense
,Sp. Atk
,Sp. Def
,Speed
)を抜き出して,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\)の正規化後の値は
となる.また,データ\(x \in X\)の標準化後の値は
$\(
\frac{x - \mu_{X}}{\sigma_{X}}
\)$
となる.
これを踏まえると,例えば,データフレームpokemon_df
のTotal
の値を正規化して,その値をnormalized_total
として格納するコードは以下のように書ける.
pokemon_df['normalized_total'] = (pokemon_df.Total - pokemon_df.Total.min()) / (pokemon_df.Total.max() - pokemon_df.Total.min())
また,データフレームpokemon_df
のTotal
の値を標準化して,その値を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で読み込んだデータフレームを機械学習用にデータ分割するには,以下の手順を踏む.
データフレームから注目する説明変数(特徴量)と目的変数を取り出す
(必要なら)データの前処理
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)