データ読み込み
共通処理(第3部で共通のインポートと設定)
- データ分析で繰り返し使うライブラリのインポートと表示設定を最初にまとめて行う。
# 日本語化ライブラリ導入
!pip install japanize-matplotlib | tail -n 1
# ライブラリのimport
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd
from IPython.display import display
# 表示オプション調整
np.set_printoptions(
suppress=True, precision=4, floatmode='fixed'
)
plt.rcParams["font.size"] = 14
plt.rcParams['figure.figsize'] = (6, 6)
plt.rcParams['axes.grid'] = True
pd.options.display.float_format = '{:.4f}'.format
pd.set_option("display.max_columns", None)
CSVファイルの読み込み
- データの内容を事前に
!headコマンドで確認し、適切なオプションを判断する。
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/bridges/bridges.data.version1'
# ファイルダウンロードと内容確認
!wget -nc $url
!head -2 bridges.data.version1
- ヘッダ行がない場合は
header=Noneとnamesで列名を指定する。
columns = [
'ID', 'RIVER', 'LOCATION', 'ERECTED', 'PURPOSE',
'LENGTH', 'LANES', 'CLEAR-G', 'T-OR-D', 'MATERIAL',
'SPAN', 'REL-L', 'TYPE'
]
df1 = pd.read_csv(
url, header=None, names=columns)
display(df1.head())
-
na_valuesオプションで特定の文字列(例:?)を欠損値として扱う。
df2 = pd.read_csv(
url, na_values='?', header=None,
names=columns)
display(df2.head())
-
index_colオプションで特定の列を行インデックスに指定する。
df3 = pd.read_csv(
url, na_values='?', header=None,
names=columns, index_col='ID')
display(df3.head())
- 区切り文字がセミコロンの場合は
sep=';'を指定する。true_values/false_valuesで yes/no をブール値に変換する。
df = pd.read_csv(
fn,
sep=';',
na_values='unknown',
true_values=['yes'],
false_values=['no'])
display(df.head(2))
- TSVファイル(タブ区切り)の読み込みは
sep='\t'を使う(演習問題)。
CSV・Excelファイルへの出力
-
to_csvでCSVファイルとして保存する。index=Falseで行インデックスを出力しない。
# 行インデックスをファイルに含めない場合
df2.to_csv('bridge2.csv', index=False)
# 行インデックスをファイルに含める場合(index_colで読み込んだ場合)
df3.to_csv('bridge3.csv')
-
to_excelでExcelファイルとして保存する。
df2.to_excel('bridge2.xlsx', index=False)
データ前処理
項目名の変更
-
columns属性に新しい項目名リストを代入することで列名を一括変更する。 -
copy()でオリジナルデータに影響を与えないようにする。
cols_jp = [
'川コード', '位置', '竣工年', '目的', '長さ', '車線数',
'垂直クリアランス', '道路位置', '建築資材', '長さ区分', '相対長', '橋種別'
]
df2 = df1.copy()
df2.columns = cols_jp
display(df2.head())
欠損値の確認
-
isnull()で各要素がNULLかどうかを判定する(True/False)。 -
isnull().sum()で項目ごとの欠損値の件数を確認する(重要パターン)。
# 各要素ごとのNULL判定
df2.isnull()
# 項目ごとの欠損値件数(メソッドチェイン)
df2.isnull().sum()
欠損値の除去
-
dropna(subset=[...])で指定した列に欠損値がある行を削除する。
df3 = df2.copy()
df3 = df3.dropna(subset=['位置'])
df3.isnull().sum()
データ型の確認と変換
-
dtypesで各列のデータ型を確認する。 - 本来整数型の列が浮動小数点型になっている場合は、欠損値の存在を疑う。
-
astype()でデータ型を変換する(欠損値を除去してから行う必要がある)。
# データ型確認
df3.dtypes
# 「位置」のデータ型を整数型に変換(欠損値除去済みが前提)
df3['位置'] = df3['位置'].astype('int')
print(df3.dtypes)
統計量の計算 (describeメソッド)
-
describe()で数値型項目の基本統計量(件数、平均、標準偏差、最小値、四分位数、最大値)を取得する。 -
describe(include='O')で文字列型(object型)項目の統計量(件数、ユニーク数、最頻値、頻度)を取得する。
# 数値項目の統計量
df2.describe()
# 文字列型項目の統計量
df2.describe(include='O')
値の出現回数 (value_countsメソッド)
-
value_counts()で特定の列の値ごとの出現回数をカウントする。
df2['建築資材'].value_counts()
特徴量計算 (mapメソッド)
- 変換用の関数を定義し、
map()メソッドで全行に適用して新しい列を作成する。
# 西暦年から竣工年区分を返す関数
def get_year_cd(x):
thres = [1850, 1900, 1950]
thres_np = np.array(thres)
return (thres_np < x).sum() + 1
# map関数ですべての行に適用
df3['竣工年区分'] = df3['竣工年'].map(get_year_cd)
display(df3.head(2))
display(df3.tail(4))
データ集計
項目の順番入れ替え
- 列名リストの操作で項目の順番を変更し、データフレームに適用する。
columns1 = list(df2.columns)
# 最後の項目を先頭に移動し、後ろの9項目を削除
columns2 = columns1[-1:] + columns1[:-9]
df2 = df2[columns2]
グループごとの集計 (groupbyメソッド)
-
groupby()で特定の列をキーにグループ化し、集約関数(mean,sumなど)を適用する。 -
sort_values()でソートを組み合わせる。
# 学歴による集計(平均値)
df_gr1 = df2.groupby('学歴').mean()
display(df_gr1)
# 職業による集計(平均値)、特定列で降順ソート
df_gr2 = df2.groupby('職業').mean(
).sort_values('今回販促結果', ascending=False)
display(df_gr2)
出現頻度のクロス集計 (crosstabメソッド)
-
pd.crosstab()で2軸の出現頻度を集計する。margins=Trueで合計行・列を追加する。 -
normalize='index'で行方向の比率計算を行う。
# 頻度集計
df_crosstab = pd.crosstab(
index=df2['職業'],
columns=df2['学歴'],
margins=True)
display(df_crosstab)
# 行方向の比率計算
df_crosstab2 = pd.crosstab(
index=df2['職業'],
columns=df2['学歴'],
normalize='index',
margins=True)
display(df_crosstab2)
項目値のクロス集計 (pivot_tableメソッド)
-
pivot_table()で2軸を指定し、特定の値について集約(mean,sumなど)を行う。
df_pivot = df2.pivot_table(
index='職業',
columns='学歴',
values='今回販促結果',
aggfunc='mean')
display(df_pivot)
データ可視化
ヒストグラム (histメソッド)
-
df.hist()でデータフレーム内の数値型項目のヒストグラムを一括表示する。 -
binsでビン数、layoutでレイアウトを指定する。
# 簡易版
df.hist()
plt.show()
# レイアウトを調整した版
plt.rcParams['figure.figsize'] = (15, 4)
df.hist(bins=20, layout=(1, 4))
plt.tight_layout()
plt.show()
棒グラフによる頻度表示 (plotメソッド)
-
value_counts().plot(kind='bar')でカテゴリ項目の頻度を棒グラフで表示する。
plt.rcParams['figure.figsize'] = (4, 4)
c = '長さ区分'
df[c].value_counts().plot(kind='bar', title=c)
plt.show()
-
dtypes == objectで文字列型の列だけを抽出し、ループで全カテゴリ項目の棒グラフを一括表示する。
# データ型がobjectの項目のみ抽出
col = df.columns
col2 = col[df.dtypes == object]
df2 = df[col2]
# ループで全カテゴリ項目の棒グラフ表示
plt.rcParams['figure.figsize'] = (12, 8)
for i, c in enumerate(df2.columns):
ax = plt.subplot(2, 4, i+1)
df2[c].value_counts().plot(
kind='bar', title=c, ax=ax)
plt.tight_layout()
plt.show()
箱ひげ図 (seaborn boxplot関数)
-
seabornライブラリのboxplot()でカテゴリ別の分布を可視化する。 -
orderで表示順を指定する。
import seaborn as sns
plt.rcParams['figure.figsize'] = (6, 6)
sns.boxplot(
x='建築資材', y='竣工年', data=df,
order=['WOOD', 'IRON', 'STEEL'])
plt.title('建築資材と竣工年の関係')
plt.show()
散布図 (seaborn scatterplot関数)
-
sns.scatterplot()でhueによる色分け、styleによるマーカー変更、sでサイズ指定ができる。
plt.rcParams['figure.figsize'] = (6, 6)
sns.scatterplot(
x='竣工年', y='長さ', data=df, hue='建築資材',
hue_order=['WOOD', 'IRON', 'STEEL'],
s=150, style='建築資材')
plt.title('竣工年と長さの関係')
plt.show()
ヒストグラム (seaborn histplot関数)
-
sns.histplot()でhueによるカテゴリ分け付きヒストグラムを描画する。 -
multiple='dodge'で並列表示、shrinkでバーの幅を調整する。
plt.rcParams['figure.figsize'] = (8, 4)
sns.histplot(
data=df, x='竣工年', hue='建築資材',
palette=['blue', 'cyan', 'grey'], multiple='dodge',
shrink=0.7)
plt.title('竣工年による建築資材の推移')
plt.show()
ヒートマップ (seaborn heatmap関数)
- クロス集計表を
sns.heatmap()で可視化する。 -
square=Trueで正方形、annot=Trueで数値表示、fmt='d'で整数表記にする。
# クロス集計表の生成
pv = pd.crosstab(
index=df['長さ区分'], columns=df['橋種別'])
display(pv)
# ヒートマップ表示
plt.rcParams['figure.figsize'] = (6, 6)
sns.heatmap(
pv, square=True, annot=True,
fmt='d', cmap='Blues', cbar=False)
xlabel = pv.columns.name
ylabel = pv.index.name
plt.title(xlabel + ' vs ' + ylabel)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.show()
データ検索・結合
ソート (sort_valuesメソッド)
-
sort_values()で特定の列を基準にデータフレームをソートする。 -
ascending=Falseで降順ソートにする。 -
inplace=Trueで元のデータフレームを直接変更する。
# 貸出日時で昇順ソート
df3 = df1.sort_values('貸出日時')
display(df3.head())
# 降順ソート(inplaceで直接変更)
df11.sort_values('貸出日時', inplace=True)
インデックスの初期化 (reset_indexメソッド)
- ソートや検索後にインデックスを振り直すには
reset_index(drop=True)を使う。 -
drop=Trueを指定しないと、旧インデックスがデータ列として残る。
# ソート後のインデックス初期化
df4 = df3.reset_index(drop=True)
display(df4.head())
-
groupby後のインデックスを列に戻すにはreset_index()を使う(dropなし)。
# カテゴリ別の売上集計
df5 = df4[['カテゴリ名', 'レンタル代']]
df6 = df5.groupby('カテゴリ名').sum()
# カテゴリ名をインデックスから列に戻す
df7 = df6.reset_index()
# 売上の多い順にソート
df8 = df7.sort_values('レンタル代', ascending=False)
display(df8.head(2))
特定の行参照 (loc / iloc)
-
locはインデックスのラベル値で参照する。行と列のラベルを同時に指定できる。
# 1行全体の参照(ラベルが1の行)
print(df3.loc[1])
# 行ラベルと列名の同時指定
print(df3.loc[1, '貸出日時'])
-
ilocは位置(整数インデックス)で参照する。NumPy的なアクセスが可能。
# 位置1(0始まりで2番目)の行全体
print(df3.iloc[1])
# 位置1の行、位置8の列
print(df3.iloc[1, 8])
-
ilocを使ったループ処理で各行のデータを辞書的にアクセスする。
for i in range(5):
x = df3.iloc[i]
rental = x['貸出ID']
customer = x['顧客ID']
title = x['タイトル']
amount = x['レンタル代']
rental_date = x['貸出日時']
print(i, rental, customer, title, amount, rental_date)
検索 (queryメソッド)
-
query()で条件式を文字列として渡し、データを絞り込む。
# 単純な検索
x1 = df4.query('顧客ID == 459')
print(len(x1))
display(x1.head())
-
and/orで複数条件を組み合わせた複合検索ができる。
x2 = df4.query('顧客ID == 459 and レンタル代 >= 4.0')
print(len(x2))
display(x2.head())
- Python変数の値を検索条件に使うには
@変数名と記述する。
cust_id = df4.iloc[1]['顧客ID']
# @変数名で Python変数を参照
x3 = df4.query('顧客ID == @cust_id')
display(x3.head())
- リスト変数を使って
in条件で複数値にマッチさせる。
cust_ids = list(df4.iloc[:3]['顧客ID'])
# in @変数名 でリスト変数を参照
x4 = df4.query('顧客ID in @cust_ids')
display(x4.head())
結合 (merge関数)
-
pd.merge()で共通のキー列を基に2つのデータフレームを結合する(SQLのJOINに相当)。
# 貸出情報から必要な項目のみ抽出
df9 = df4[['貸出ID', '顧客ID', '貸出日時', 'タイトル']]
# 顧客情報から必要な項目のみ抽出
df10 = df2[['顧客ID', '名', '姓']]
# 顧客IDをキーに結合
df11 = pd.merge(df9, df10, on='顧客ID')
# 結合結果をソート
df11.sort_values('貸出日時', inplace=True)
display(df11.head())
日付データの処理
read_csvのparse_datesオプション
-
parse_datesで指定した列を自動的にdatetime型に変換する。
url1 = 'https://github.com/makaishi2/samples/raw/main/data/rental5-jp.csv'
# 5番目の列(0始まり4番目)をdatetime型で読み込む
df1 = pd.read_csv(url1, parse_dates=[4])
# ソートとインデックス初期化
df2 = df1.sort_values('貸出日時')
df2 = df2.reset_index(drop=True)
# データ型確認(貸出日時がdatetime64[ns]になる)
print(df2.dtypes)
display(df2.head(2))
日付集計用の項目追加
-
datetimeモジュールとmapメソッドを使い、日付を「日単位」「週単位」に変換する関数を定義して新しい列を追加する。
from datetime import datetime
# 週単位の日付作成
def conv_week_day(ts):
year, week, day = ts.isocalendar()
str = f'{year} {week} 1'
return datetime.strptime(str, "%Y %W %w")
# 日単位の日付作成
def conv_date(ts):
str = ts.isoformat()
return datetime.strptime(str[:10], '%Y-%m-%d')
df3 = df2.copy()
# insert メソッドで指定位置に列を追加
df3.insert(4, '貸出日', df3['貸出日時'].map(conv_date))
df3.insert(5, '貸出週', df3['貸出日時'].map(conv_week_day))
display(df3.head(2))
- 月単位・年単位への変換関数も同様に定義できる。
# 月単位の日付作成
def conv_month(ts):
str = ts.isoformat()
return datetime.strptime(str[:7], '%Y-%m')
# 年単位の日付作成
def conv_year(ts):
str = ts.isoformat()
return datetime.strptime(str[:4], "%Y")
週単位の集計
-
groupbyとsumで日付単位の集計を行う。
df4 = df3.groupby('貸出週')['レンタル代'].sum()
display(df4)
-
pd.date_range()で一定間隔の日付インデックスを生成する。 - 空のSeries変数を用意し、集計結果をマッピングする。
# 1週間単位のインデックス作成
date_index = pd.date_range(
"2005-05-23", periods=14, freq="W-MON")
print(date_index)
# 空の集計表作成
rent_fare = pd.Series(0, index=date_index)
# 売上集計
for ts in df4.index:
rent_fare[ts] += df4[ts]
print(rent_fare)
集計結果の可視化
-
Series.plot(kind='bar')で棒グラフを描画する。
plt.rcParams['figure.figsize'] = (6, 6)
rent_fare.plot(kind='bar')
plt.title('週単位の売上合計')
plt.show()
日付範囲の指定による検索
-
pd.to_datetime()で文字列をdatetime型に変換し、queryメソッドの条件に使う。
# 開始日・終了日をdatetime型で定義
sday = pd.to_datetime('2005-06-11')
eday = pd.to_datetime('2005-06-18')
uid = 459
# queryメソッドで日付範囲指定の検索
x2 = df3.query(
'顧客ID == @uid and 貸出日 >=@sday and 貸出日 <= @eday')
display(x2)
日付への加減算 (relativedelta関数)
-
dateutil.relativedeltaで日付の加減算を行う(日、週、月、年単位)。
from dateutil.relativedelta import relativedelta
# 基準日付
t1 = x2['貸出日'].iloc[0]
# 4日後
ts = t1 + relativedelta(days=4)
# 14日後
te = t1 + relativedelta(days=14)
print(t1, type(t1))
print(ts, type(ts))
print(te, type(te))
- 計算結果をそのまま
queryメソッドの検索条件に使える。
x3 = df3.query('顧客ID == @uid and 貸出日 >=@ts and 貸出日 <= @te')
display(x3)