まずpandasモジュール(パッケージ)をインポートする.慣例に従い pd という別名をつけておく.
import pandas as pd
import numpy as np
データ読み込み
次に,データを読む.urlを直接入れてWeb経由でも読むことができる.
ここでは UCI機械学習レポジトリ https://archive.ics.uci.edu/ml/ からiris(あやめ)のデータを直接読んでみる.
ブラウザでurl http://logopt.com/data/iris.data/ をみてデータを確認すると,列の名前(ヘッダー)がついておらず、データだけがカンマ区切りで並んでいるようだ.
これはcsv (comma-separated valueの略)ファイルと呼ばれるタイプのテキストファイルなので,read_csv関数で読むことができる.返値はデータを表形式で保管するデータ構造であるデータフレームである.(ここでは df という名前の変数に保管しておく.)
ついでに列名をnames引数で指定してあげよう.これは列名を表す文字列のリストとして与える. データは順に「'がく片長','がく片幅','花びら長','花びら幅', '種類'」である.
# import ssl
# ssl._create_default_https_context = ssl._create_unverified_context
df = pd.read_csv(
"http://logopt.com/data/iris.data", names=["がく片長", "がく片幅", "花びら長", "花びら幅", "種類"]
)
df.head() # headメソッドで最初の5つだけを表示(最後を表示するにはtailを使う;やってみよう!)
もう1つの例題としてUFOの目撃情報のデータを読み込んでみよう。データはhttp://logopt.com/data/ufo.csv にある。
このデータはヘッダーが付いているので、namesで列名を指定せずに読み込むことができる。ただし、最初の列が抜けているので、Unnamed:0 と表示される。
ufo = pd.read_csv("http://logopt.com/data/ufo.csv")
ufo.head()
問題(ワイン)
ワインに関するデータセットから,wineデータを読み込んで,wineという名前でデータフレームに保管せよ.
元データはこちらに格納されている.
http://logopt.com/data/wine.data
列名は https://archive.ics.uci.edu/ml/datasets/Wine で解説されているが,必要ならば以下のリストを用いて,列名を設定して読み込め.
L = [ 'Alcohol', 'Malic','Ash', 'Alcalinity', 'Magnesium', 'Phenols', 'Flavanoids', 'Nonflavanoid', 'Proanthocyanins', 'Color', 'Hue', 'OD280', 'OD315', 'Proline']
さらに,最後の5つのデータを表示させて確認せよ.
問題(ビデオゲーム)
ビデオゲームのセールスデータを http://logopt.com/data/vgsales.csv から読みこみ、データフレームに保管せよ。ただし、このデータにはヘッダーが付いている。
問題(車)
車の燃費に関するデータセットから,Auto MPGデータを読み込んで,carという名前でデータフレームに保管せよ.
元データはこちらに格納されている.
http://logopt.com/data/auto-mpg.data
データを確認してみると,このデータはカンマ(,)区切り(これがread_csv関数の既定値)ではなく,空白で区切られている.
このような場合には,read_csvの引数の delim_whitespace をTrueに設定しておく必要がある.
列名は https://archive.ics.uci.edu/ml/datasets/Auto+MPG で解説されているが,必要ならば以下のリストを用いて,列名を設定して読み込め.
L = ['mpg', 'cylinders', 'displacement', 'horsepower','weight', 'acceleration','year','origin', 'name']
さらに,最初と最後の5つのデータを表示させて確認せよ.
df = pd.read_csv(
"http://logopt.com/data/iris.data", names=["がく片長", "がく片幅", "花びら長", "花びら幅", "種類"]
)
print(df.index) # インデックスは0から149までの整数であることが表示される.
df.head()
インデックスとなる列を指定しないと、上のように0から始める整数が自動的に付加される。インデックス列を指定するには、read_csvを用いてデータを読み込む際に、index_col引数で指定することができる。列の番号もしくは列名を与えることによって、指定した列がインデックスになる。
UFOの目撃情報のデータで最初の列(0番目の列)をインデックスとして指定して読み込んでみる。
ufo = pd.read_csv("http://logopt.com/data/ufo.csv", index_col=0) # column
ufo.head()
列の名前は,番上に表示されている太字の行であり,これはcolumns属性でアクセスできる.
ufo.columns
データ自身は,データフレームのvalues属性に保管されている.これはNumPyの多次元配列(ndarray)である(type関数を用いて確認してみる). したがって,データの最初の行を表示させるには,df.values[0]とすればよい. 0行4列目(最初のアヤメの名前)を表示させるには,df.values[0,4](もしくは df.values[0][4])とすればよい.
print(type(df.values)) # values属性はNumPyのn次元配列である.
df.values[0, 4] # 0行4列目の要素は 'Iris-sentosa'である.
df.values[0, 4]
データの概要を知るためのメソッドがdescribe()である.(メソッドなので関数と同じように最後に ()を付けるのを忘れずに.)
df.describe() # count(データ数),mean(平均),std(標準偏差),min(最小値)など
df["がく片長"][3:10] # 列名が'がく片長'の列の最初の9個のデータから成るシリーズ(series:インデックスとデータの組)
df.がく片長[:3]
行の切り出しはインデックスで切り出す. たとえば,1行目から3行目までを切り出すには,以下のようにする.
df[1:4]
行列の要素(Excelのセルに相当)へのアクセス
行と列を指定して要素を抽出するには,ilocもしくはloc属性を用いる.
文法はいずれも同じで,以下の通り.
df.iloc[行の切り出し,列の切り出し] #番号でのアクセス
df.loc[行の切り出し,列の切り出し] #ラベルを用いたアクセス
行と列はラベルもしくは番号でアクセスできる.ラベルとは,行に対してはインデックス,列に対しては列名を指す.
- ilocは番号によるアクセスを行う.
切り出しは,リストと同様に,番号 $i:j$ とすると $i$番目から$j-1$番目までが抽出される.
- locはラベルによるアクセスを行う.
切り出しは,リストやilocと異なり,境界も含んだものになる.すなわち,$i:j$ とするとラベル $i$からラベル $j$ までが抽出される.
切り出しを行うかわりに,抽出したい列名のリストを用いて,['がく片長', '花びら幅']などと切り出しをしてもよい.
通常のスライシングと同様に,すべての行もしくは列を抽出したい場合には,$ : $ と記述すればよい.
たとえば,1列目から2列目までから成るデータフレームを切り出すには,
df.iloc[ : , 1:3]
とすればよい.
df.iloc[1:5, 1:4] # 1行目から4行目まで,1列目から3列目までを抽出
df.loc[1:5, "がく片長":"花びら幅"] # 行をインデックスで,}列を列名で指定(最後が含まれることに注意!)
df.loc[1:5, ["がく片長", "花びら幅"]] # 列をリストで指定
jp = pd.read_csv("http://logopt.com/data/50on.csv", index_col=0)
jp
行は番号のスライシングで切り出すことができる.
jp[2:4]
列はラベルでアクセスできる.
jp["カ列"]
ラベルのリストを与えることによって複数の列を切り出せる.
jp[["カ列", "タ列"]]
ilocメソッドを使うと行番号と列番号,もしくは行番号と列番号のスライシングで値を抽出できる.スライシングは終了の番号の値は含まないことに注意.
jp.iloc[2, 2]
jp.iloc[2:4, 1:3]
locメソッドを使うと行ラベルと列ラベル,もしくは行ラベルと列ラベルのスライシングで値を抽出できる.スライシングは終了のラベルの値を含むことに注意.
jp.loc["ウ行", "サ列"]
jp.loc["ウ行":"エ行", "カ列":"サ列"]
L = [
"mpg",
"cylinders",
"displacement",
"horsepower",
"weight",
"acceleration",
"year",
"origin",
"name",
]
car = pd.read_csv(
"http://logopt.com/data/auto-mpg.data", delim_whitespace=True, names=L
)
car.sort_values("mpg", ascending=False).head()
df[df.がく片長 >= 7.0]
df[(df.がく片長 >= 7.0) & (df.花びら長 >= 5.0)]
df[(df.がく片長 < 4.8) | (df.花びら長 < 1.3)]
df.groupby("種類").sum()
df.groupby("種類").describe()
import pandas as pd
df = pd.read_csv("http://logopt.com/data/class.csv")
df
df.groupby("クラス名").sum()
df.groupby(["クラス名", "性別"])[["身長", "体重"]].agg(["sum", "max"])
drinks = pd.read_csv("http://logopt.com/data/drinks.csv")
drinks.head()
drinks = pd.read_csv("http://logopt.com/data/drinks.csv")
drinks.head()
drinks.drop("continent", axis=1).head() # 大陸(continent)の列を削除
drinks.drop(2, axis=0).head() # インデックス2の行を削除
drinks.drop([3, 5], axis=0).head() # インデックス3が,5の行を削除したいときには、引数にリストを入れる
ufo = pd.read_csv("http://logopt.com/data/ufo.csv", index_col=0)
ufo.tail()
データには多くのNaN(欠損値)を含まれる.
欠損値のある行を削除するには dropnaメソッド を用いる.
ufo.dropna().tail()
欠損値を適当な値で置き換えたい場合にはfillnaメソッドを用いる.
たとえば,欠損値NaNを'Unknown'で置き換えたい場合には,以下のようにする.
ufo.fillna("Unknown").tail()
ufo.mode()
これはデータフレームであるので,1行目は ufo.mode().iloc[0]で抽出できる.
これを fillnaメソッドの引数で与えると,NaNが各列の最頻値(モード)に置換される. このテクニックは,機械学習の前処理でしばしば使われる.
ufo.fillna(ufo.mode().iloc[0]).tail()
df = pd.DataFrame(
{
"name": ["Kitty", "Pika", "Dora"],
"favorite": ["apple", "apple", "dorayaki"],
"friend": [np.nan, "Satoshi", "Nobita"],
"birthday": [np.nan, np.nan, np.nan],
}
)
df
普通に dropna を使うと、全部消してしまう。dropnaのaxis引数の既定値は 0 (もしくは "index") であるので、各行を調べて NaN がある行は全て消してしまうからだ。 誕生日を表す列 birthday は、全員が不明なので、何も残らないのだ。
df.dropna()
引数の axisを1(もしくは列を表す "columns")にすると、列に NaN が含まれているものが削除される。 好みの食べ物を表す列 favorite だけが残る。
df.dropna(axis=1) # or axis="columns"
dropnaメソッドの引数には how (どのようにして) というのがある。既定値は "any" であり、いずれかの要素が NaN なら削除される。 howに "all" を指定すると、すべての要素が NaN のものだけ削除される。 すべての列が NaN の人はいないので、元のデータフレームと同じものが得られる。
df.dropna(how="all")
列に対して howを "all" に指定して、dropnaメソッドを呼ぶと、birthday列が削除される。 誕生日列 birthday はすべて NaN だからだ。
df.dropna(axis=1, how="all")
drinks = pd.read_csv("http://logopt.com/data/drinks.csv")
drinks.dtypes
beer_servingの列は整数になっているが,astypeメソッドを用いて,データ型を浮動小数点数floatに変えてみよう.
drinks["beer_servings"] = drinks.beer_servings.astype(float)
drinks.dtypes
read_csv関数で読み込むときに,データ型を指定することもできる.
引数のdtypeに列名をキー,データ型を値とした辞書で指定する.
drinks = pd.read_csv(
"http://bit.ly/drinksbycountry",
dtype={"beer_servings": float, "spirit_servings": float},
)
drinks.dtypes
drinks.head()
データに文字列の操作を行いたいときには,strを付加してから,文字列のメソッドを書く.
たとえば,国名(country)を大文字に変換したいときには,str.upper()とする.
drinks["country"] = drinks.country.str.upper()
drinks.head()
ufo = pd.read_csv("http://logopt.com/data/ufo.csv", index_col=0)
ufo.dtypes
Time列のデータ型は一般の object となっているようだ.
これを日付時刻型 datetime に変換するには,pandasのpd.to_datetime関数を用いる.
変換した列を新たに DateTime 列に保管しておく.
ufo["DateTime"] = pd.to_datetime(ufo.Time)
ufo.head()
ufo.dtypes # データ型を確認
time_delta = ufo.DateTime.max() - ufo.DateTime.min()
time_delta.seconds
インデックスを日付時刻型をもつDateTimeに設定する.
ufo.set_index("DateTime", inplace=True)
ufo.head()
日付時刻型をもつインデックスに対しては,resampleによって指定した時間幅で集約できる.以下では月次に集約し,その件数を計算し,プロットしている.
ufo.resample("M").count().plot()
D = {"name": ["Pikacyu", "Mickey", "Kitty"], "color": ["Yellow", "Black", "White"]}
pd.DataFrame(D)
リストのリスト(入れ子のリスト)として与えることもできるが,列名は別途columnsで与える必要がある.
L = [["Pikacyu", "Yellow"], ["Mickey", "Black"], ["Kitty", "White"]]
pd.DataFrame(L, columns=["name", "color"])
NumPyの配列からもデータフレームを生成できる.
例として2つのサイコロを5回づつ振ったときの目をランダムに生成した配列に代入し,そこからデータフレームを生成する.
Dice = np.random.randint(1, 7, size=(5, 2)) # 引数の(low, high)はhighを含まないことに注意
dicedf = pd.DataFrame(Dice, columns=["dice1", "dice2"])
dicedf
同様に,コインを5回投げたときの表裏を0,1で表した配列を生成する.
Coin = np.random.randint(0, 2, size=(5, 2)) # 引数の(low, high)はhighを含まないことに注意
coindf = pd.DataFrame(Coin, columns=["coin1", "coin2"])
coindf
2つのデータフレームをconcatを用いて合体させる.列方向で合併したいので,axis=1と設定する.
pd.concat([dicedf, coindf], axis=1)
D1 = {"name": ["スヌー", "チーズ", "ドラ"], "おもちゃ": ["積木", "人形", "ロボット"]}
D2 = {"name": ["ケッタイ", "モロ", "ドラ"], "ぬいぐるみ": ["猫", "狼", "狸"]}
df1 = pd.DataFrame(D1)
df2 = pd.DataFrame(D2)
df1
df2
pd.merge(df1, df2, on="name", how="inner")
pd.merge(df1, df2, on="name", how="outer")
pd.merge(df1, df2, on="name", how="left")
ピボットテーブル
ビデオゲームのセールスデータ"http://logopt.com/data/vgsales.csv" をピボットテーブルを用いて集計する.
pandasのpivot_table関数は,引数としてデータフレーム,集計する値(values),行(index),列(columns),集計関数(aggfunc)を与えると,ピボットテーブルを返す.
例としてビデオゲームのデータに対して,行を年('Year'),列をジャンル('Genre')とし,世界中での売り上げ('Global_Sales')を合計('sum')したピボットテーブルを生成する.
集計の方法は引数aggfuncで与える.既定値はNumPyのmean(平均)である.
import pandas as pd
sales = pd.read_csv("http://logopt.com/data/vgsales.csv")
sales.head()
pivot = pd.pivot_table(
sales, values="Global_Sales", index="Year", columns="Genre", aggfunc="sum"
)
pivot.head() # ピボットテーブル自身がデータフレームオブジェクトなので,最初の5行だけ表示するにはheadメソッドが使える.
%matplotlib inline
pivot.plot()
# ピボットテーブル自身がデータフレームオブジェクトなので,plotメソッドで描画もできる.
df = pd.read_csv("http://logopt.com/data/class.csv")
df
df.pivot_table(index="クラス名", columns="性別", values="身長", aggfunc=[sum, max])
pivot = df.pivot_table(index=["クラス名", "性別"], values="身長", aggfunc=sum)
pivot
pivot.xs("猫組", axis=0, level=0)
問題(ポケモン)
ポケモンデータ http://logopt.com/data/Pokemon.csv" に対して,メインタイプ('Type 1')と世代('Generation')別の攻撃力('Attack')と守備力('Defense')の平均を集計せよ.
(ヒント:pivot_tableで,集計値に複数の値を設定するには,引数valuesにデータフレームの列名をリストとして与える.)
poke = pd.read_csv("http://logopt.com/data/Pokemon.csv", index_col=0)
poke.head()
問題(映画)
映画のデータ "http://logopt.com/data/movie_metadata.csv" に対して,主演俳優の列('actor_1_name')がジョニー・デップ('Johnny Depp')のものを抽出し,年度('title_year')別の予算('budget')と興行収入('gross')を線グラフで表示せよ.
(ヒント:行には年度を,列には何も指定しないでピボットテーブルを生成し,plotメソッドでグラフを生成する.)
movie = pd.read_csv("http://logopt.com/data/movie_metadata.csv")
movie.head()
forループによる反復処理
Pandasでは、できるだけ反復処理はしない方が高速に処理ができるが、反復処理をした方が簡単にプログラムが書けることもある。 大規模なデータを扱うのでなければ、多少の処理時間はかかっても、コードを書く時間を短縮した方が良い場合が多い。 ここでは、そのような場合に用いる反復処理の方法について学ぶ。
データフレームにfor文を用いて反復を行う最も簡単で効率的な方法は、itertuplesメソッドを用いる方法であり、 このように記述する。
for 行 in データフレーム.itertuples():
反復の中身
(行は列名を属性とした名前付きタプル)
映画のデータ "http://logopt.com/data/movie_metadata.csv" を用いた、以下の例題を考える。
あなたは最近観た面白い映画の名前をど忘れした。確か、タイトルに"Super"が入っていた低予算映画だったと思うのだが、 そのディレクターを無性に知りたくなった。どうやって検索すれば良いだろうか?
itertuplesを用いた反復を使って、タイトルに"Super"が入っていて、予算が100000以下のものを検索する。
movie = pd.read_csv("http://logopt.com/data/movie_metadata.csv")
for row in movie.itertuples():
if "Super" in row.movie_title and row.budget <= 100000:
print(row.movie_title, row.director_name)
そうだ、映画のタイトルは "Super Size Me" だった。ディレクターも無事に判明した。
今度は、多少高速な方法で検索してみよう。これは、必要な列だけを切り出してきて、それをzip関数で合わせたものを作成して反復を行う。
for title, budget, director in zip(
movie.movie_title, movie.budget, movie.director_name
):
if "Super" in title and budget <= 100000:
print(title, director)
同じ結果が得られた。いずれの方法でも良いが、itertuplesを用いた方が簡単で可読性も良い。 一方。列の切り出しとzipを用いた方法は、列がたくさんあるときには高速になる。
問題(ポケモン)
ポケモンデータ http://logopt.com/data/Pokemon.csv" に対して,伝説のポケモン('Legendary'が真)で、攻撃力('Attack')が防御力('Defense')より小さく、攻撃力が $90$ 以下、速度(’Speed’)が $110$ 以上のものを探せ。
poke = pd.read_csv("http://logopt.com/data/Pokemon.csv", index_col=0)
poke.head()
ufo = pd.read_csv("http://logopt.com/data/ufo.csv", index_col=0)
ufo.rename(columns={"Time": "日付", "Shape Reported": "形"}, inplace=True)
ufo.head()