Kaggle「Pandas」講座の内容整理(1)- Lesson 1-3
データ整形・加工に必須のPandas活用法を体系的に整理。Kaggle公開コース「Pandas」を要約し要点を補足。本記事はLesson 1–3(作成/読み書き、選択、集約)を解説。
KaggleのPandas講座で学んだ内容をここに整理します。
分量が多いため 2 回に分けました。
- 第1編: Lesson 1-3(本文)
- 第2編: Lesson 4-6
Lesson 1. Creating, Reading and Writing
Pandasの読み込み
1
import pandas as pd
Pandas にはデータフレーム(DataFrame)とシリーズ(Series)という 2 つの中核オブジェクトがある。
データフレーム
データフレーム(DataFrame)は表、あるいは行列と考えられる。独立したエントリ(entries)で構成される行列で、各エントリは特定の値(value)を持ち、1 つの行(row)またはレコード(record)、そして 1 つの列(column)に対応する。
1
pd.DataFrame({'Yes': [50, 21], 'No': [131, 2]})
| Yes | No | |
|---|---|---|
| 0 | 50 | 131 |
| 1 | 21 | 2 |
データフレームのエントリは必ずしも数値である必要はない。次は文字列の値(ユーザーのレビュー)を持つデータフレームの例。
1
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'], 'Sue': ['Pretty good.', 'Bland.']})
| Bob | Sue | |
|---|---|---|
| 0 | I liked it. | Pretty good. |
| 1 | It was awful. | Bland. |
データフレームを生成するときは pd.DataFrame() コンストラクタ(constructor)を使い、Python の辞書(dictionary)記法で宣言する。キー(key)に列名、値(value)に各列の項目を並べたリスト(list)を渡す。これは新しいデータフレームを宣言する標準的な方法である。
データフレーム宣言時、列ラベルには列名を指定するが、行ラベルを別途指定しない場合は 0, 1, 2, … の整数が割り当てられる。必要に応じて行ラベルを手動で指定できる。データフレームにおける行ラベルのリストをインデックス(Index)と呼び、コンストラクタの index 引数で指定できる。
1
2
3
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'],
'Sue': ['Pretty good.', 'Bland.']},
index=['Product A', 'Product B'])
| Bob | Sue | |
|---|---|---|
| Product A | I liked it. | Pretty good. |
| Product B | It was awful. | Bland. |
シリーズ
シリーズ(Series)はデータ値からなる数列(sequence)、あるいはベクトルである。
1
pd.Series([1, 2, 3, 4, 5])
シリーズは本質的にデータフレームの単一列と同じである。したがって同様にインデックスを指定でき、単に「列名」の代わりに「名前」(name)を持つだけである。
1
pd.Series([30, 35, 40], index=['12015 Sales', '12016 Sales', '12017 Sales'], name='Product A')
1
2
3
4
12015 Sales 30
12016 Sales 35
12017 Sales 40
Name: Product A, dtype: int64
シリーズとデータフレームは密接に関連している。データフレームをシリーズの束と考えると理解しやすい。
データファイルの読み込み
多くの場合、データは自分で作るのではなく既存のデータを取り込んで使う。データはさまざまな形式で保存されるが、最も基本的なのは CSV ファイルである。典型的な CSV の中身は次のようになる。
Product A,Product B,Product C,
30,21,9,
35,34,1,
41,11,11
すなわち CSV は各値をカンマ(comma)で区切る表である。名前の由来は “Comma-Separated Values”、CSV だ。
CSV 形式のデータをデータフレームに読み込むにはpd.read_csv()関数を使う。
1
product_reviews = pd.read_csv("../input/product-reviews/example-data.csv")
shape属性でデータフレームの形状を確認できる。
1
product_reviews.shape
1
(129971, 14)
この出力は 129971 行、14 列を持つことを意味する。
head()メソッドで先頭 5 行を確認できる。
1
product_reviews.head()
pd.read_csv() には 30 個を超える引数がある。たとえば読み込む CSV が独自のインデックス列を含む場合、index_col 引数を指定して自動採番の代わりにその列をインデックスとして使える。
1
product_reviews = pd.read_csv("../input/product-reviews/example-data.csv", index_col=0)
データファイルの書き出し
to_csv()メソッドでデータフレームを CSV に書き出せる。使い方は次のとおり。
1
product_reviews.to_csv("../output/product-reviews/example-data.csv")
Lesson 2. Indexing, Selecting & Assigning
Pandas のデータフレームやシリーズから特定の値を選ぶ操作は、ほぼすべてのデータ処理で通る工程なので、必要なデータポイントを素早く効率よく選ぶ方法を優先して身につけるべきである。
Python標準のアクセス方法
ネイティブ Python オブジェクトは優れたデータのインデクシング手段を提供しており、Pandas もそれらを同様に提供する。
オブジェクト属性
Python ではオブジェクトのプロパティ(property)に属性名(attribute)でアクセスできる。たとえば example_obj が title 属性を持つなら example_obj.title と呼べる。Pandas データフレームの列にも同様にアクセスできる。
1
reviews.country
1
2
3
4
5
6
0 Italy
1 Portugal
...
129969 France
129970 France
Name: country, Length: 129971, dtype: object
辞書型のインデクシング
また Python の辞書型はインデクシング演算子([])で値にアクセスできる。Pandas データフレームの列にも同じ方法でアクセスできる。
1
reviews['country']
1
2
3
4
5
6
0 Italy
1 Portugal
...
129969 France
129970 France
Name: country, Length: 129971, dtype: object
属性アクセスと辞書式アクセスはいずれも有効だが、辞書式は空白のような属性名に使えない文字(例: reviews['country providence'] は可だが reviews.country providence は不可)を含む列名も扱える利点がある。
取得した Pandas シリーズの中でも、さらにインデクシング演算子で個々の値にアクセスできる。
1
reviews['country'][0]
1
'Italy'
Pandas固有のアクセサ
上のようなインデクシングや属性アクセスは他の Python エコシステムと自然に馴染む点で優れているが、Pandas にはこれ以外に独自のアクセサであるlocとilocがある。
インデックス(位置)ベースの選択
iloc ではインデックス(位置)ベースの選択(index-based selection)を行う。データ内の位置を整数で指定して取り出す。
たとえばデータフレームの最初の行は次のように選べる。
1
reviews.iloc[0]
1
2
3
4
5
6
country Italy
description Aromas include tropical fruit, broom, brimston...
...
variety White Blend
winery Nicosia
Name: 0, Length: 13, dtype: object
ネイティブ Python の「まず列を選び、その後に行を選ぶ」流儀と異なり、iloc は「まず行、その後に列」を選ぶ。データフレームの最初の列は次のように選べる。
1
reviews.iloc[:, 0]
1
2
3
4
5
6
0 Italy
1 Portugal
...
129969 France
129970 France
Name: country, Length: 129971, dtype: object
ここでは : 演算子で全行を選んだうえで、最初の列を選んでいる。最初の列のうち 2 行目(1)と 3 行目(2)だけ選びたいなら次のとおり。
1
reviews.iloc[1:3, 0]
1
2
3
1 Portugal
2 US
Name: country, dtype: object
リストを渡すこともできる。
1
reviews.iloc[[1, 2], 0]
1
2
3
1 Portugal
2 US
Name: country, dtype: object
負のインデックスで末尾側から選ぶことも可能。次は末尾 5 行を選ぶ例。
1
reviews.iloc[-5:]
ラベルベースの選択
もう一つの方法は loc を使うラベルベースの選択(label-based selection)である。位置ではなく、インデックスの値で選ぶ。
たとえばインデックス値が 0 の行の ‘country’ 列のエントリは次のように得られる。
1
reviews.loc[0, 'country']
1
'Italy'
iloc はデータセットのインデックス値を無視して 1 つの大きな行列のようにみなし、位置に基づいてエントリにアクセスする。一方 loc はインデックス情報を活用して動作する。多くの場合、インデックス自体にも意味のある情報があり、loc のほうが iloc より直感的なことが多い。
iloc と loc のスライス範囲の違い
iloc は Python 標準ライブラリのインデクシング規則に従い、0:10 は 0 以上 10 未満の半開区間、すなわち 0,...,9 を意味する。
一方 loc は区間を閉区間として解釈するので、0:10 は 0 以上 10 以下、すなわち 0,...,10 を意味する。
この差がある理由は、loc は整数だけでなくあらゆる標準的な型をインデックスに使えるためである。たとえばインデックスが ‘Apples, …, Potatoes, …’ のような並びで、この範囲(アルファベット順)に該当する作物を選ぶとき、’Potatoes’ の直後にあり得る文字列 ‘Potatoet’ を作って「’Apples’ から ‘Potatoet’ の手前まで」(df.loc['Apples':'Potatoet'])と指定するより、単に「’Apples’ から ‘Potatoes’ まで」(df.loc['Apples':'Potatoes'])と書けたほうが直感的だ。整数以外の型のインデックスでは後者のほうが一般に直感的なので、loc はこの規則を採用している。
それ以外の動作は基本的に同じである。
個人的には、昇順の整数インデックスを持つデータセットで
:による範囲指定をする場合、上記の範囲規則の違いによる混乱を避けるためにilocを、それ以外ではより直感的なlocを使うことが多い。
インデックスの操作
必要に応じてインデックスを調整できる。set_index()を使うと、次のようにデータ内の特定の列を新しいインデックスにできる。
1
reviews.set_index("title")
条件付き選択
ここまでの内容はデータフレーム自体の構造的な属性を使ってデータを加工・選択する方法だった。さらに踏み込んで、より複雑な条件を満たすデータだけを選ぶこともできる。
たとえばワイン製品のデータフレームから、評価が 90 点以上のイタリア産ワインだけを選ぶとしよう。
1
reviews.country == 'Italy'
この条件式は True/False のブール値からなるシリーズを返す。
1
2
3
4
5
6
0 True
1 False
...
129969 False
129970 False
Name: country, Length: 129971, dtype: bool
loc は基本的にラベルベースだが、ブール配列やソート可能なブールシリーズも受け付ける。したがって次のようにイタリア産ワインだけを選べる。
1
reviews.loc[reviews.country == 'Italy']
複数条件は & や | で結合できる。イタリア産「かつ」評価が 90 点以上なら次のとおり。
1
reviews.loc[(reviews.country == 'Italy') & (reviews.points >= 90)]
イタリア産「または」評価が 90 点以上なら次のとおり。
1
reviews.loc[(reviews.country == 'Italy') | (reviews.points >= 90)]
Pandas にはいくつかの組み込み条件セレクタがあり、代表的なのが isin と isnull / notnull である。
isinは、指定したリスト「の中にある(is in)」値かどうかを表すブールのマスクシリーズを返し、これでデータを選別できる。たとえばイタリア産またはフランス産のワインは次のように選べる。
1
reviews.loc[reviews.country.isin(['Italy', 'France'])]
isna/notnaは欠損値(NaN)を持つか否かで絞り込むときに使う。たとえば価格が欠損していないワインだけ選ぶには次のとおり。
1
reviews.loc[reviews.price.notna()]
ちなみに Kaggle 講座には出てこないが、
ilocもブール配列(array)を受け付ける。ただしlocと異なり配列のみ対応でシリーズは不可のため、上のような応用は難しい。
データの代入
データフレームに新しいデータを代入したり、上書きしたりできる。
1
2
reviews['critic'] = 'everyone'
reviews['critic']
1
2
3
4
5
6
0 everyone
1 everyone
...
129969 everyone
129970 everyone
Name: critic, Length: 129971, dtype: object
1
2
reviews['index_backwards'] = range(len(reviews), 0, -1)
reviews['index_backwards']
1
2
3
4
5
6
0 129971
1 129970
...
129969 2
129970 1
Name: index_backwards, Length: 129971, dtype: int64
Lesson 3. Summary Functions and Maps
データ概要の確認
describe()は、指定した列の高レベルな要約を提供する。
1
reviews.points.describe()
1
2
3
4
5
6
count 129971.000000
mean 88.447138
...
75% 91.000000
max 100.000000
Name: points, Length: 8, dtype: float64
describe() の出力は入力の型によって異なる。数値ではない文字列データに対しては次のような出力になる。
1
reviews.taster_name.describe()
1
2
3
4
5
count 103727
unique 19
top Roger Voss
freq 25514
Name: taster_name, dtype: object
特定の統計量だけを取り出すこともできる。
1
reviews.points.mean()
1
88.44713820775404
1
reviews.taster_name.unique()
1
2
3
4
5
6
7
array(['Kerin O’Keefe', 'Roger Voss', 'Paul Gregutt',
'Alexander Peartree', 'Michael Schachner', 'Anna Lee C. Iijima',
'Virginie Boone', 'Matt Kettmann', nan, 'Sean P. Sullivan',
'Jim Gordon', 'Joe Czerwinski', 'Anne Krebiehl\xa0MW',
'Lauren Buzzeo', 'Mike DeSimone', 'Jeff Jenssen',
'Susan Kostrzewa', 'Carrie Dykes', 'Fiona Adams',
'Christina Pickard'], dtype=object)
データフレーム内で各ユニーク値の出現回数を知りたい場合は、value_counts()を使う。
1
reviews.taster_name.value_counts()
1
2
3
4
5
6
Roger Voss 25514
Michael Schachner 15134
...
Fiona Adams 27
Christina Pickard 6
Name: taster_name, Length: 19, dtype: int64
写像(Maps)
写像(map)は数学の用語で、一方の集合を別の集合へ対応させる関数を意味する。データサイエンスでは、与えられたデータを別の表現形式に変換する必要がしばしばあり、その際に写像を使うため非常に重要である。
主に次の 2 つのメソッドを使う場面が多い。
Series.map()は 1 つの値を別の単一値に変換する関数を受け取り、その関数を指定したシリーズ内のすべての値に一括適用し、新しいシリーズを返す。たとえばワインの評価から一律に平均値を引いて偏差を得たいなら次のとおり。
1
2
review_points_mean = reviews.points.mean()
reviews.points.map(lambda p: p - review_points_mean)
1
2
3
4
5
6
0 -1.447138
1 -1.447138
...
129969 1.552862
129970 1.552862
Name: points, Length: 129971, dtype: float64
DataFrame.apply()は各行に対してカスタム関数を呼び出し、データフレーム全体に変換を適用したいときに使う。
1
2
3
4
5
def remean_points(row):
row.points = row.points - review_points_mean
return row
reviews.apply(remean_points, axis='columns')
apply() を axis='index' で呼べば、行ではなく列に対して関数を適用できる。
Series.map() と DataFrame.apply() は、それぞれ変換後の新しいシリーズとデータフレームを返し、元のデータは変更しない。
| メソッド | Series.map() | DataFrame.apply() |
|---|---|---|
| 適用対象 | シリーズ | データフレーム |
| 適用単位 | 個々の値に適用 (シリーズを列ベクトルと見れば行方向に適用) | 既定では行方向に適用 オプション指定で列方向も可 |
ちなみに
Series.apply()とDataFrame.map()も存在する。
Series.apply():
by_row='compat'(既定):Series.map()と同様に動作by_row=False: シリーズ全体を一度に関数へ渡す(axis='index'指定時のDataFrame.apply()に類似)DataFrame.map():データフレーム内の個々の値に関数を適用(対象がシリーズかデータフレームか以外はSeries.map()に近い)
実のところ、Pandas にはよく使う写像が多数組み込まれている。上の例は次の、より簡潔なコードでも実現でき、この場合も Pandas は意図どおりに動作する。
1
2
review_points_mean = reviews.points.mean()
reviews.points - review_points_mean
1
2
3
4
5
6
0 -1.447138
1 -1.447138
...
129969 1.552862
129970 1.552862
Name: points, Length: 129971, dtype: float64
さらに、Pandas は同じ長さのシリーズ同士の演算もサポートする。ワインの例で、原産国と産地の情報を次のように文字列連結することも可能だ。
1
reviews.country + " - " + reviews.region_1
1
2
3
4
5
6
0 Italy - Etna
1 NaN
...
129969 France - Alsace
129970 France - Alsace
Length: 129971, dtype: object
これらの演算は Pandas の内蔵ベクトル化による高速化を利用するため、map() や apply() より高速であり、Pandas はすべての Python 標準の演算子(>, <, == など)に対してこの方式で動作できる。それでも map() と apply() はより柔軟で複雑な処理に対応できるため、使い分けを知っておくと有用である。

