Dataquest
2-1 Pandas and NumPy Fundamentals
Introduction to NumPy
NumPy はデータを ndarray(n-dimensional array)で持っている
data_ndarray = np.array([10, 20, 30])
- なぜ ndarray が分析を楽にするのか
- CPU の SIMD(Single Instruction Multiple Data)を使って高速に計算できるから
- このように複数のデータを一度に操作することを Vectorization(ベクトル化)という
Exploring Data with pandas: Fundamentals
- ベクトル計算のメリット
- パフォーマンスに優れる
- 計算処理の記述が簡素になる (
series_a - series_b
など)
- 統計関数を使うと便利
(dataframe|series).max()
(dataframe|series).min()
(dataframe|series).mean()
(dataframe|series).median()
(dataframe|series).mode()
(dataframe|series).sum()
series.value_counts()
--- ユニークな値とその出現数を取得するseries.describe()
--- 統計情報を取得する- null でない値の個数
- 平均、最大値、出現頻度など
Exploring Data with pandas: Intermediate
- 明示的に NaN をセットしたいときは
np.nan
を使う - インデックス列を列番号で指定する方法
pd.read_csv('some.csv', index_col=0)
- 指定しなければ連番がセットされる
- インデックス「名」を削除する
df.index.name = None
- インデックス|カラムによる選択を行う
df.loc[]
- 位置による選択を行う
df.iloc[]
- null であるものを抽出する
df.isnull()
df.notnull()
series.isnull()
series.notnull()
- boolean index を組み合わせて使う
is_john = df['name'] == 'john'
adult = df['age'] > 18
df[is_john & adult] # and
df[is_john & ~adult] # not
df[is_john | adult] # or - 並べ替える
df.sort_index(ascending=False)
df.sort_values('age', ascending=False)
- ユニークな値を配列として取得する
df['firstname'].unique()
Data Cleaning Basics
データを俯瞰する
まずdf.info()
により、カラム名、格納されているデータの数と種類などを俯瞰する
カラム名を整理する
カラム名の変更はdf.columns
を置き換えることにより行う
- 記号を削除するか置き換える ---
str.replace()
- 先頭・末尾のスペースを削除する ---
str.strip()
- スペースをアンダーバーで置き換える ---
str.replace()
- 大文字を小文字にする ---
str.lower()
- 長い名前を短い名前にする ---
str.replace()
データの俯瞰
df.describe()
で統計情報を俯瞰したうえで下記を抽出する- 数値がテキストとして格納されていないか --- あれば修正する(後述)
- 値が 1 種類しかない無用な列はないか --- あれば削除を検討する
df.drop(['colname'], axis=1)
- より詳しい調査が必要な 列はどれか --- あれば調査する
必要に応じて文字列列を数値列にする
- 列ごとに値のパターンと例外を調べる
Series.dtype
Series.unique()
- non-digit な部分を取り除く
- Vectorized string methodを用いて行う
Series.str.replace('GB','')
など
- 型を変換する
Series.astype(float)
- 必要に応じてカラム名を変更する(各セルに含まれていた
GB
のような単位情報を付与するなど)df.rename({'ram':'ram_gb'}, axis='columns', inplace=True)
文字列列を整理する
- 必要があれば文字列の一部を使って新しい列を作成する
# `cpu`列に`Intel Core i5 2.3GHz`のような値が格納されている場合
laptops["cpu_manufacturer"] = (
laptops["cpu"].str.split().str[0]
) - 表記のゆらぎを修正する
s.value_counts()
s.map({'wrong': 'correct'})
--- マップにはすべての値を含める必要があるので注意する
- 文字列の一部を抜き出す
2016-03-26 17:47:46
という形式の列から日付を抜き出すにはautos['date_crawled'].str[:10]
日付列
- 日付データを扱う時の下準備
df['mydate'] = pd.to_datetime(df['mydate'])
外れ値
- あまりにも大きい or 小さい値を除外する
- よく使うメソッド
- 何種類の値があるか ---
Series.unique().shape
- 最大値、最小値、中央値、平均値はどうか ---
Series.describe()
- 出現頻度が高すぎる値はないか ---
Series.value_counts().head()
- 出現頻度が低すぎる値はないか ---
Series.value_counts().sort_values(ascending=False).head()
- 値の分布を%で見て不自然なところはないか ---
Series.value_counts(normalize=True, dropna=False)
- 何種類の値があるか ---
- 除外する際は
.between()
で boolean index を作ると、読みやすくて便利。df[df["col"].between(x,y)]
欠損値の処理
# 列ごとの欠損値数を把握する
df.isnull().sum()
# 特 定の列における欠損値の出現数を確認する
df['os_version'].value_counts(dropna=False)
# 欠損値のある行に関連する他の列の値を見る。
# その結果に応じて、欠損値の値を埋めたり、そのままにするかを決める。
df.loc[df["os_version"].isnull(), "os"].value_counts()
- 対応方法 1: 欠損値を含む行や列を削除する
df.dropna(axis=0)
df.dropna(axis=1)
- 対応方法 2: 値を埋める
- 対応方法 3: 何もしない
値の種類が多すぎる列の削除
df.describe(include='all')
したときに、freq が count に限りなく近い場合、無意味なデータでは ないか確認する。
Guided Project: Exploring Ebay Car Sales Data
集計
例えばブランドごとの価格の中央値を算出したいとき
top20_brands = autos['brand'].value_counts()[:20]
brand_mean_prices = {}
for brand in top20_brands.index:
mean = autos.loc[autos['brand']==brand, 'price'].mean()
brand_mean_prices[brand]=mean
このままでは扱いにくいのでシリーズにしてから使う
pd.Series(
brand_mean_prices,
name="mean_price",
)
2-2 Exploratory Data Visualization
基本
グラフの描写にはmatprotlib.pyplot
を使う。
- データを使って plot を作成する
- plot の見た目を調整する
- plot を表示する
- 満足するまで繰り返す
import matprotlib.pyplot as plt
# データをセット(折れ線グラフの場合)
plt.plot(df['DATE'], df['VALUE'])
# ラベルの回転角度
plt.xticks(rotation=90)
# ラベル
plt.xlabel('Month')
plt.ylabel('Unemployment Rate')
# タイトル
plt.title('Monthly Unemployment Trends, 1948')
# グラフの描写
plt.show()
Line Charts(折れ線グラフ)
- 「連続値(日時等) × 数値」のデータに最適
- 変化を見る
# (x_values, y_values)
plt.plot(df['DATE'], df['VALUE'])
Multiple plots
matplotlib のクラスを理解する
- Figure Object
plt.figure()
で作成する- 一番親玉のコンテナであり、複数の Axes を内包する
- 描写領域を確保する
- dpi、背景色、線色などを管理する
- Axes Object
figure.add_subplot(縦分割数, 横分割数, 分割した何番目に配置するか)
で作成する- 実際のデータを描写する
- データ、軸、軸ラベルなどを持つ
axes.plot(x_values, y_values)
でデータをプロットする(plt.plot()
の実体である)
手動でグラフを作る
plt.plot()
を使った場合は自動的に figure と axes が作成されているが、カスタマイズ等する場合はこれらを手動で作る必要がある- axes を手動で生成したとき、デフォルトでは
- x 軸 y 軸ともに値のレンジが 0 から 1 になる
- グリッドラインは表示されない
- データは 表示されない
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1)
ax2 = fig.add_subplot(2,1,2)
# figureとaxesを同時に作る方法もある
# fig, ax = plt.subplots()
# fig, axs = plt.subplots(2, 2)
ax1.plot(x_values1, y_values1)
ax2.plot(x_values2, y_values2)
ax1.set_xlabel('...') # .xlabel()ではない点に注意
ax1.set_ylabel('...') # .ylabel()ではない点に注意
ax1.set_title('...') # .title()ではない点に注意
plt.show()
複数のデータを同じ axes に描写する
plt.figure()
- 複数回呼んでも figure は一つしか作られない
plt.plot()
- 初回 --- axes を作成したうえでラインを追加する
- 2 回目以降 --- 初回に作成した axes にラインを追加する
上記を踏まえると、下記のようにグラフを描写できる。
plt.figure(figsize=(5,3)) # サイズ指定などのカスタマイズはここで行える
plt.plot(...)
plt.plot(...)
plt.plot(...)
plt.show()
# だけど、こっちのほうが明示的で読みやすいかも?
fig = plt.figure(figsize=(5,3))
axes = fig.add_subplot(1,1,1)
axes.plot(...)
axes.plot(...)
plt.show()
Figure のサイズ調整
# インチで指定
plt.figure(figsize=(12, 5))
色の指定
plt.plot(c='red')
凡例
plt.plot(label='1948')
plt.legend(loc='upper left')
Bar Plots(棒グラフ)
- 「クラス(離散値) × 数値」のデータに最適
- クラスをまたいで最大値、最小値などを比較するために使う
- クラスが多すぎると見にくくなるので注意
- 縦棒グラフなら
axes.bar()
、横棒グラフならaxes.barh()
を使う - 以下、縦棒グラフの場合で説明
# バーの開始位置
# => [0.75, 1.75, 2.75, 3.75, 4.75]
from numpy import arange
bar_positions = arange(5) + 0.75
# 実際に表示するデータ群
bar_height = [2,5,3,5,7]
# バーの幅
bar_width = 0.5
# X軸ラベルの位置を数値の配列で指定
axes.set_xticks([1,2,3,4,5])
# X軸ラベルに表示する値を指定
axes.set_xticklabels(['a','b','c','d','e'], rotation=90)
# バーを表示する
axes.bar(bar_positions, bar_height, bar_width)
Scatter Plots(散布図)
- 「数値 × 数値」のデータに最適
- 相関を調べるのに使う
axes.scatter(x_values, y_values)
# X軸とY軸の下限、上限をセットする
axes.set_xlim(0, 5)
axes.set_ylim(0, 5)
Histograms(ヒストグラム)
- ヒストグラム --- 値の区切り(bin)を作って、そこに含まれるデータの数を観察する
ヒストグラム | 棒グラフ | |
---|---|---|
目的 | 連続値の視覚化 | 離散値の視覚化 |
X 軸上のバーの位置 | 重要な意味を持つ | 意味を持たない |
axes.hist(
values,
bins=20, # 区切りの数
range=(0,5), # 横軸(値)の表示範囲
)
axes.set_ylim(0, 100) # 縦軸(出現数)の表示範囲
Box Plots(ボックスプロット)
- 値がどのように分布しているかを把握するために使う
- box-and-whisker(箱とひげ)を用いて四分位でデータ表現する(参考図)
- box で表現される真ん中の二分位を interquartile range または IQR という
- ボックスの長さ(IQR)と髭の長さを比べることで、分布の様子がわかる
- ひげの書き方には変種がある。デフォルトでは後者で描写される。
- 方法 1:最小値と最大値をひげの端にする
- 方法 2:第 1or 第 3 四分位点から 1.5IQR 以上離れた値を外れ値(outlier, extreme values)とし、残った値の端をひげの端とする
values = [2,3,4,5,2,2,3,5]
axes.boxplot(values)
axes.set_ylim(0, 5)
axes.set_xticklabels(['some label'])
# 一つのaxis内に複数のbox plotを並べたいときは、
# DataFrame.values を使ってarray of arrayにして値を渡す
target_columns = ['RT_user_norm', 'Metacritic_user_nom', 'IMDB_norm', 'Fandango_Ratingvalue']
values = df[target_columns].values # => [[1,2,3,4], [2,1,3,4],,,,]
axes.boxplot(values)
axes.set_xticklabels(target_columns)
Guided Project: Visualizing Earnings Based On College Majors
DataFrame から直接グラフを作る方法
- DataFrame や Series を基にしてグラフを描写できる
- matplotlib を直接使うときと異なり、多くの設定を省略できる
- カスタマイズが必要な場合は axes をいじることで行う
# セットアップ
%matplotlib inline
# 散布図
axes = df.plot(
x='column1',
y='column2',
kind='scatter',
title='Employed vs. Unemployed',
figsize=(5,10),
)
# 棒グラフ
axes = df['column1'].plot(kind='bar')
axes = df.plot.bar(x='column1', y='column2')
# ヒストグラム
axes = df['column1'].hist()
Scatter matrix
- 複数の列に関して、ヒストグラムと散布図をひと目で俯瞰できる図
from pandas.plotting import scatter_matrix
scatter_matrix(df[['Age','Weight','Sex']], figsize=(10,10))
2-3 Storytelling Through Data Visualization
Improving Plot Aesthetics
見やすいグラフに必要なこと
- 良いグラフは、説明するのではなく比較を促す
- 注目したいデータと対になるデータを追加する
- 政党支持率だけでなく不支持率も合わせて表記するなど
- 不要な要素(chartjunk)を取り除く
- data-ink ratio を高める
- data-ink とは、折れ線グラフなら線、散布図なら点の部分。それ以外の要素をなるべく減らせ。
- non data-ink の部分に一貫性を持たせ、比較を容易にすること
- 例えば上限値・下限値を揃えるなど
tick mark(棘)を消す
axes.tick_params(
bottom='off',
top='off',
left='off',
right='off',
# ラベルを消したい時は`label`をつける
labelbottom='off'
)
spine(軸線)を消す
spines は dict で管理されている。それぞれでset_visible()
を実行する。
print(axes.spines)
# {'bottom': <matplotlib.spines.Spine object at 0x7fcaa3476ac8>,
# 'top': <matplotlib.spines.Spine object at 0x7fcaa2f534e0>,
# 'right': <matplotlib.spines.Spine object at 0x7fcaa34762b0>,
# 'left': <matplotlib.spines.Spine object at 0x7fcaa3476390>}
for spine in axes.spines.values():
spine.set_visible(False)
Color, Layout, and Annotations
- Color Blind 10 など色盲に配慮した配色にする
- 線は適度に太くする
ax.plot(
# 色
c=(255/255, 128/255, 14/255),
# 先の太さ
linewidth=3
)
- 凡例は実データのすぐそばに書く(x, y はデータの値で指定する)
ax.text(x, y, "Starting Point")
- 補助線を入れる
ax.axhline(
50, # y軸上の位置
c=(171/255, 171/255, 171/255),
alpha=0.3)
Guided Project: Visualizing The Gender Gap In College Degrees
画像ファイルとして出力する
plt.savefig('biology_degrees.png')
Conditional Plots
- seaborn とは matplotlib のラッパー
- matplotlib を直接使う場合と比べて、楽にきれいなグラフを作ることができる。
- seaborn では null の値はエラーになるので取り除いておくこと
Histogram & kernel density plot
import seaborn as sns
import matplotlib.pyplot as plt
# Histogram & kernel density plot
sns.distplot(df["MyColumn"])
# kernel density plotのみ
sns.kdeplot(
titanic["Age"],
shade=True, # 塗りつぶしたい場合
)
plt.show()
- スタイルを設定できる
- data-ink ratio を上げるためのおすすめ設定は以下の通り
# 白背景にする
sns.set_style("white")
# 全てのspineを消す(デフォルトでは左と下は残る)
sns.despine(left=True, bottom=True)
FacetGrid
- クラス分けにより分割した複数のデータサブセットを元に複数のグラフを作るには
FacetGrid
を使う- はじめから data-ink ratio の高いグラフが生成される
- 目盛りがあらかじめ統一されている
FacetGrid
- 第一引数: DataFrame
col
: クラス分けに使用する列名(この列のユニークな値の数だけグラフが作られる)size
: グラフの高さ
FacetGrid.map()
- 第一引数: matplotlib or seaborn の関数(
sns.kdeplot
,plt.hist
など) - 第二引数: 列名
- 第一引数: matplotlib or seaborn の関数(
# 1 つのクラス分けを使ってグラフを作る場合(横一列に並ぶ)
g = sns.FacetGrid(
titanic,
col="Survived",
size=6)
# 2 つのクラス分けを使ってグラフを作る場合(縦横に並ぶ)
g = sns.FacetGrid(
titanic,
col="Survived",
row="Pclass",
size=12)
# 3 つのクラス分けを使ってグラフを作る場合(縦横に並んだうえで2つのグラフが重ねて表示される)
g = sns.FacetGrid(
titanic,
col="Survived",
row="Pclass",
hue="Sex",
size=12).add_legend()
g.map(sns.kdeplot, "Age", shade=True)
Visualizing Geographic Data
- 緯度経度の情報を二次元にマップするには
basemap toolkit
を使う - これは Matplotlib の拡張で、緯度経度(球面座標)を平面座標(デカルト座標)にマップするためのもの
散布図の描写
basemap = Basemap(
projection='merc', # メルカトル図法
llcrnrlon=-180, # 左下の経度
llcrnrlat=-80, # 左下の緯度
urcrnrlon=180, # 右上の経度
urcrnrlat=80, # 右上の緯度
)
# BasemapインスタンスはListしか受け付けないのであらかじめ変換しておく
longitudes = df["longitude"].tolist()
latitudes = df["latitude"].tolist()
# 平面座標に変換する
x_positions, y_positions = basemap(longitudes, latitudes)
# basemapはmatplotlibを使っているので
# カスタマイズはmaptplotlibと同じ方法で行える
plt.figure(figsize=(15,20))
plt.title("Scaled Up Earth With Coastlines")
# 海岸線を描写する
basemap.drawcoastlines()
# データを散布図として描写する
basemap.scatter(
x_positions,
y_positions,
s=1, # マーカーのサイズ
)
plt.show()
効率的な軌跡(great circle)の描写
マップの端をまたぐ軌跡や、差が 180 度を超える軌跡は描写できないので注意
basemap.drawgreatcircle(
start_lon,
start_lat,
end_lon,
end_lat,
)