まずpandasパッケージをインポートする.慣例に従い pd という別名をつけておく.
import pandas as pd
import numpy as np
データ読み込み
次にデータを読む. URLを直接入れてWeb経由でも読むことができる.
ここでは UCI機械学習レポジトリ https://archive.ics.uci.edu/ml/ で配布されているiris(あやめ)のデータを読んでみる.
ブラウザで 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://logopt.com/data/drinks.csv",
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 を用いた反復を使って、タイトル(movie_title)に"Super"が入っていて、予算(budget列の数字)が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()
簡易分析パッケージ PandasGUI
最近では,pandasのデータフレームをGUIで分析できるツールが出てきている.
例として,PandasGUI https://github.com/adamerose/pandasgui を紹介する.
このパッケージには,ポケモンデータを含む代表的なデータセットが入っている. すべてのデータセット all_datasets をインポートして,それをshow関数を用いて表示する.
from pandasgui import show
from pandasgui.datasets import iris, all_datasets
all_datasets.keys()
サンプルデータの概要
- 'pokemon': ポケモン
- 'googleplaystore': Google Play Store
- 'googleplaystore_reviews': Google Play Storeのレビュー
- 'netflix_titles': Netflixの番組タイトルやキャスト
- 'trump_tweets': トランプのツイート
- 'harry_potter_characters': ハリーポッターのキャラクター
- 'happiness': 国別の幸福度
- 'country_indicators': 国の指標
- 'us_shooting_incidents': 米国の銃の事件
- 'stockdata': 株価
- 'gapminder': 国別の寿命やGDPの変化
- 'anscombe': 可視化の重要性を理解するための4つのデータ
- 'attention': 注意とスコア
- 'brain_networks': 脳ネットワーク
- 'diamonds': ダイヤモンドの価格
- 'dots': 発火率
- 'exercise': エクササイズ
- 'flights': 航空機の乗客数
- 'fmri': Magnetic resonance imaging (MRI)データ
- 'gammas': gamma反応
- 'geyser': 噴火期間と待ち時間
- 'iris': アヤメ
- 'mpg': 車の燃費
- 'penguins': ペンギン
- 'planets': 惑星
- 'tips': チップ
- 'titanic': タイタニック号での生存確率
- 'seinfeld_episodes': ドラマ Seinfeld のエピソード
- 'seinfeld_scripts': ドラマ Seinfeld の台詞
- 'mi_manufacturing': 製造データ(複数インデックス,複数列)
- 'simple': 小さなデータ
- 'multiindex': 複数インデックスの小さなデータ
- 'small': 小さな例題
- 'unhashable': データに複合データ(リストやタプルや辞書)の情報を含んだデータ
show(**all_datasets);
#irisデータだけを表示する場合
#show(iris);
簡易分析パッケージ pandas-profiling
GUIベースのものが嫌いな人は, htmlを生成する pandas-profiling https://github.com/ydataai/pandas-profiling を使うとよい.
ProfileReportクラスをインポートして,そこにpandasのデータフレームを入れると分析したhtmlファイルが生成される.
以下に,irisデータを入れて相関分析のタブを押したときの画面を示す.
from pandas_profiling import ProfileReport
ProfileReport(iris)
簡易分析パッケージ pygwalker
より洗練された(tableau形式の)GUIとしてpygwalker https://github.com/Kanaries/pygwalker がある.
Juputer上だけでなく, streamlitにも簡単に組み込める.
以下に,irisデータに対する散布図の描画の例を示す.
import pygwalker as pyg
irisgui = pyg.walk(iris)