#準備(すべてに共通) # PandasとNumpyをインポート import pandas as pd import numpy as np # 日本語化Matplotlibもインポート import matplotlib.pyplot as plt !pip install japanize-matplotlib import japanize_matplotlib # Seabornもインポート import seaborn as sns # pickleをインポート(モデルの保存用) import pickle # データの取得 data = pd.read_csv("https://www2.cmds.kobe-u.ac.jp/~masa-n/dshandson/bike-sharing-day.csv") data
データが構造化された表にちゃんと収まっているかを確認する
data.shape
data.dtypes data.info()
data.isnull().sum()
data["列1"].value_counts()
分析用のデータセットの作成に向けて,データの型やインデクスを整えていく
df = data.copy()
#列を任意の型変換する df["列1"] = df["列1"].astype("型名") #日時型への変換 df["日時列2"] = pd.to_datetime(df["日時列2"])
df.set_index("列1", inplace=True)
df.drop(columns=["列1", "列2", ...], inplace=True)
#型を確認 data.info()
#オリジナルデータをコピーして作業 df = data.copy()
#dtedayを日付型に変換 df["dteday"] = pd.to_datetime(df["dteday"]) #確認 df.info() df
#season, holiday, weekday, workingday, weathersitをカテゴリ型に変換 for col in ["season", "holiday", "weekday", "workingday", "weathersit"]: df[col] = df[col].astype("category") #確認 df.info() df
#dtedayをインデクスに設定して時系列データにする df.set_index("dteday", inplace=True) #確認 df.info() df
#instant, yr, mnth: 冗長なので削除 df.drop(columns=["instant", "yr", "mnth"], inplace=True) #確認 df.info() df
様々な観点からデータを探索(要約・可視化)して,データが持つ性質を理解する
可視化にはpythonのライブラリを使う
df.describe()
df["列1"].value_counts()
df.plot.box() sns.boxplot(df) #Seabornを使う場合
df["列1"].plot.hist() sns.histplot(df["列1"]) #Seabornを使う場合
sns.violinplot(df)
df.plot.bar() sns.barplot(df) #Seabornを使う場合
df["列1"].plot() df["列1"].resample("周期").集約関数().plot() #週や月で時系列を集約して表示する場合 sns.lineplot(df) #Seabornを使う場合
#describe()は基本的に数値列のみを要約する df.describe()
#カテゴリの列を要約 for col in ["season", "holiday", "weekday", "workingday", "weathersit"]: print(f"\n【{col}の要約】") print(df[col].value_counts())
#気象データの分布を箱ひげ図で可視化 sns.boxplot(df[["temp", "atemp", "hum", "windspeed"]])
#利用者データの分布を箱ひげ図で可視化 sns.boxplot(df[["casual", "registered", "cnt"]])
#各変数のヒストグラムを描いてみる.サブプロットを使って,1枚の図に並べる fig, axes= plt.subplots(nrows=4, ncols=2, tight_layout=True, squeeze=False) for i, col in enumerate(["temp", "atemp", "hum", "windspeed", "cnt", "casual", "registered"]): sns.histplot(df[col], ax=axes[i//2, i%2], bins=40)
#利用者データの推移を見てみる fig, axes = plt.subplots(nrows=2, ncols=1, tight_layout=True, squeeze=False, figsize=(6,8)) #利用者データの推移 df[["casual", "registered", "cnt"]].plot(ax=axes[0,0], title="利用者データの推移(日次)") #利用者データの月ごとの推移を棒グラフで(登録・都度の内訳) df[["casual", "registered"]].resample("M").sum().plot.bar(stacked=True, ax=axes[1,0], title="利用者データの推移(月次)")
df.corr() sns.heatmap(df.corr(), annot=True) #ヒートマップで可視化する
df.plot.scatter(x=列1, y=列2) sns.scatterplot(df, x=列1, y=列2, hue=列3) #Seabornを使う場合.hueで色分けできる
sns.pairplot(df) #変数が多いと時間がかかるし見づらい
#相関係数を求める df.corr()
#カテゴリ変数も含めたければ,get_dummies()を行う pd.get_dummies(df).corr()
#ヒートマップで可視化 sns.heatmap(df.corr(), annot=True)
#気温と総利用者数の関係.季節で色分け sns.scatterplot(df, x="temp", y="cnt", hue="season")
#ペアプロット sns.pairplot(df)
df.groupby("カテゴリ列1").describe()["数値列2"]
【比較の観点】
#統計量の要約 df.groupby(df.index.year).describe("cnt") df.groupby("season").describe("cnt") #以下同様 # : #箱ひげ図で比較する fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(18,18)) sns.boxplot(df, y="cnt", x=df.index.year, ax=axes[0,0]) sns.boxplot(df, y="cnt", x="season", ax=axes[0,1]) sns.boxplot(df, y="cnt", x=df.index.month, ax=axes[1,0]) sns.boxplot(df, y="cnt", x="weekday", ax=axes[1,1]) sns.boxplot(df, y="cnt", x="holiday", ax=axes[2,0]) sns.boxplot(df, y="cnt", x="workingday", ax=axes[2,1]) sns.boxplot(df, y="cnt", x="weathersit", ax=axes[3,0])
#バイオリンプロットでもやってみてください # 略
df.isnull().sum() #列ごとの欠損値の数を数える df[df["列1"].isnull()] #列1が欠損している行を抜き出す
df = df.dropna() df = df.dropna(subset=[列1, 列2, ...]) #特定の列のNaNのみを対象
df = df.fillna(0)
df = df.fillna(df.集約関数()) #表全体 df["列1"] = df["列1"].fillna(df["列1"].集約関数()) #列ごと
df = df.fillna(method="ffill" または "bfill") #表全体 df["列1"] = df["列1"].fillna(method="ffill" または "bfill") #列ごと
df = df.interpolate() #表全体 df["列1"] = df["列1"].interpolate() #列ごと
# 箱ひげ図や散布図から見つける
df = df.drop(index=[行1, 行2, ...])
df.loc[行1, 列1] = 正しい値
df[df.duplicated(keep=False)]
df = df.drop_duplicates()
#クリーニングされていないシェアサイクルのデータをロード data = pd.read_csv("https://www2.cmds.kobe-u.ac.jp/~masa-n/dshandson/bike-sharing-unclean.csv") #コピーしておく df = data.copy()
#欠損値の個数を数える df.isnull().sum()
#tempが欠損している行を抜き出す df[df["temp"].isnull()]
#前後を見てみる df[185:195] #行ごと削除する (dfに代入していないので,dfはそのまま) df.dropna(subset=["temp"])[185:195]
#定数0で埋める (dfに代入していないので,dfはそのまま) df.fillna(0)[185:195]
#中央値で埋める (dfに代入していないので,dfはそのまま) df.fillna(df.median())[185:195]
#線形補間で埋める (dfに代入していないので,dfはそのまま) df.interpolate()[185:195]
#すべての欠損値を線形補間で埋める df = df.interpolate()
#欠損値を数える df.isnull().sum()
#要約統計量を求める df.describe()
#怪しそうな変数を表示する df[["casual", "registered", "cnt"]].plot.box() #条件で特定してみる df[df["cnt"] > 80000]
#周辺のデータを見てみる df[510:520]
#修正する df.loc[517, "casual"] = 533 df.loc[517, "cnt"] = 4127
#再度箱ひげ図で確認 df[["casual", "registered", "cnt"]].plot.box()
#重複行を見つける df[df.duplicated(keep=False)]
#重複行を削除する df = df.drop_duplicates()
#重複行を再度確認 df[df.duplicated(keep=False)]
pd.get_dummies(df, columns=[ダミー変数化する変数のリスト], drop_first=True)
#元データを適当に作る df = pd.DataFrame(data={"物件No.":[1,2,3,4,5], "駅からの距離":[0.2,0.8,1.5,3.4,4.8], "築年数":[30, 25, 5, 20, 50], "部屋数":[1,3,3,4,6], "家賃":[55000,83000,64000,72000, 100000], "管理費":[5000, 10000, 10000, 20000, 30000]}, ).set_index("物件No.") df
from sklearn.preprocessing import StandardScaler #標準化のためのスケーラー sc = StandardScaler() #各列にフィットさせる sc.fit(df) #スケール変換(sc.transform(df))して,データフレームに入れなおす df_sc = pd.DataFrame(sc.transform(df), index=df.index,columns=df.columns) #確認 df_sc
- 各データから最小値を引き,値域の幅で割る
from sklearn.preprocessing import MinMaxScaler #正規化のためのスケーラー sc = MinMaxScaler() #各列にフィットさせる sc.fit(df) #スケール変換(sc.transform(df))して,データフレームに入れなおす df_sc = pd.DataFrame(sc.transform(df), index=df.index,columns=df.columns) #確認 df_sc
from sklearn.cluster import AgglomerativeClustering model = AgglomerativeClustering(ハイパーパラメータ) #標準化したデータフレームで学習させる model.fit(df_sc) #各データのクラスタ番号 model.labels_
import pandas as pd import numpy as np import scipy import random # 日本語化Matplotlibもインポート import matplotlib.pyplot as plt !pip install japanize-matplotlib import japanize_matplotlib import seaborn as sns
#そのままだと文字コードが読めないので,encodingを指定.CP932はshift_jisの拡張文字コード #1行目に属性のコードが入っているので,読み飛ばす data = pd.read_csv("https://www2.cmds.kobe-u.ac.jp/~masa-n/dshandson/SSDSE-C-2023.csv", encoding="CP932", skiprows=1) data
#列数が大きすぎるので,列の番号を書き出してみる for i, col in enumerate(data.columns): print(f"{i}: {col}")
: 1: 都道府県 ... 9: 生うどん・そば 10: 乾うどん・そば 11: パスタ 12: 中華麺 13: カップ麺 14: 即席麺 15: 他の麺類 ...
#1:都道府県の列,および,9:生うどん・そば ~ 14:即席麺の列を切り出す df = data.iloc[:, [1,9,10,11,12,13,14]].copy() df
#都道府県をインデクスに df = df.set_index("都道府県") df
#全国の行を削除 df = df.drop(index=["全国"]) df
#データを標準化する from sklearn.preprocessing import StandardScaler #スケーラーを学習 sc = StandardScaler() sc.fit(df) #標準化してデータフレームに入れる df_sc = pd.DataFrame(sc.transform(df), index=df.index, columns=df.columns) df_sc
#凝集型階層的クラスタリングのモデル from sklearn.cluster import AgglomerativeClustering #クラスタ数の指定なし.distance_threshold=0 とすると,最後までクラスタリングを続ける model = AgglomerativeClustering(n_clusters=None, distance_threshold=0) #標準化したデータで学習.教師なしなので,yが不要 model.fit(df_sc)
# デンドログラムを描く関数(基本的に触らないこと) # 参考:https://scikit-learn.org/stable/auto_examples/cluster/plot_agglomerative_dendrogram.html#sphx-glr-auto-examples-cluster-plot-agglomerative-dendrogram-p y from scipy.cluster.hierarchy import dendrogram def plot_dendrogram(model, **kwargs): # Create linkage matrix and then plot the dendrogram # create the counts of samples under each node counts = np.zeros(model.children_.shape[0]) n_samples = len(model.labels_) for i, merge in enumerate(model.children_): current_count = 0 for child_idx in merge: if child_idx < n_samples: current_count += 1 # leaf node else: current_count += counts[child_idx - n_samples] counts[i] = current_count linkage_matrix = np.column_stack( [model.children_, model.distances_, counts] ).astype(float) # Plot the corresponding dendrogram dendrogram(linkage_matrix, **kwargs)
#デンドログラムを描画する plot_dendrogram(model, labels=df.index)
#兵庫県と似ている県は? df.loc[["兵庫県","愛媛県", "岐阜県", "岡山県"],:]
#兵庫県と似ていない県は? df.loc[["兵庫県","大阪府", "秋田県", "香川県"],:]
#上記のデンドログラムから,クラスタ数7ぐらいに分けてみる model2 = AgglomerativeClustering(n_clusters=7) model2.fit(df_sc) #結果を確認する model2.labels_
#元データにクラスタのラベルを付けてみる df["クラスタ"] = model2.labels_ df
#各クラスタの都道府県を表示 for i in range(7): print(f"\n\n【クラスタ No. {i}】") print("\n",df[df["クラスタ"]==i])
#クラスタごとの各麺類の平均消費額 df_mean = df.groupby("クラスタ").mean() df_mean
#積み上げ棒グラフで可視化 df_mean.plot.bar(stacked=True, figsize=(8,8))
【考察】
分析者は,あらかじめ何個のクラスタに分けるかを示すハイパーパラメータ k を入力として与える
from sklearn.cluster import KMeans model = KMeans(ハイパーパラメータ) #標準化したデータフレームで学習させる model.fit(df_sc) #各データのクラスタ番号 model.labels_
#クラスタ番号が付いたデータフレームを確認 df
#標準化済みのデータを確認 df_sc
from sklearn.cluster import KMeans #K-Mean法を用いる.クラスタ数は7で与える model_km = KMeans(n_clusters=7, random_state=0) #標準化済データでフィット model_km.fit(df_sc) #ラベルを表示 model_km.labels_
#dfの列にK-Meansで分けられたクラスタ番号を入れる df["クラスタ2"] = model_km.labels_ df
#各クラスタの都道府県を表示 for i in range(7): print(f"\n\n【クラスタ2 No. {i}】") print("\n",df[df["クラスタ2"]==i])
#K-Meansで分けられたクラスタ番号でグループ化 df_mean2 = df.groupby("クラスタ2").mean() #元のクラスタの列を消す df_mean2.drop(columns=["クラスタ"], inplace=True) #積み上げ棒グラフ df_mean2.plot.bar(stacked=True,figsize=(8,8))
#麺類消費クラスタリングを色んなkでやってみる #誤差を入れるリスト dist = [] #2~47(=都道府県数)の範囲 X=range(2,48) for k in X: #k個のクラスタを作る model = KMeans(n_clusters=k, random_state=0, n_init=10) model.fit(df_sc) #その時の誤差をリストに追加 dist.append(model.inertia_) #折れ線グラフにプロット plt.plot(X, dist, marker='^') plt.grid()
#k=14としてK-meansを適用 k = 14 model = KMeans(n_clusters=k, random_state=0, n_init=10) model.fit(df_sc)
#結果をクラスタ3という列で保存 df["クラスタ3"] = model.labels_ #各クラスタの都道府県を表示 for i in range(k): print(f"\n\n【クラスタ3 No. {i}】") print("\n",df[df["クラスタ3"]==i])