時系列パート(基礎編:Pandasの基礎)#

使用データについて#

今回はカフェの顧客データcafe_customers.csv)を使用します.これは時系列データの典型例で,頻繁に扱うデータ形式です.

必要なライブラリのインポート#

pandas (pd)

  • 目的: データの読み込み,加工,分析

  • 特徴: ExcelのようなTable形式データ(DataFrame)を扱う

  • 研究での用途: 実験データの前処理,統計分析,可視化用データ準備

まずは,今回のノートブックの実行に使用するライブラリをインポートします.

import pandas as pd
from datetime import timedelta

# 警告メッセージを非表示にするライブラリ・設定
import warnings
warnings.filterwarnings('ignore')

データの読み込み#

時系列データ分析の第一歩は「データを理解すること」から始まります.

どんなに高度な分析手法を使っても、データの中身を知らなければ意味のある結果は得られません.

今回使用するデータセット(cafe_customers.csv)をpandasライブラリを用いて読み込みます.

# CSVファイルの読み込み
df = pd.read_csv('../data/raw/cafe_customers.csv')

# データの最初の5行を表示
print(df.head())

データの形状とタイプの確認#

データ分析の最重要ステップの一つです.

データの「形」と「型」を理解せずに分析を進めると,誤った結論に至る可能性があります.

なぜshapeの確認が重要なのか?#

df.shape(行数, 列数) を返します:

  • サンプルサイズの把握: 統計的有意性の判断材料

  • 処理時間の予測: 大規模データでは処理方法を変える必要がある

  • メモリ使用量の推定: 大きすぎるデータは分割処理が必要

# データの形状を表示
print(df.shape)

データ型(dtypes)の重要性#

df.dtypes でデータ型を確認する理由:

  1. 適切な分析手法の選択:

    • int64/float64: 数値分析(平均,相関など)

    • object: カテゴリ分析(クロス集計など)

    • datetime64: 時系列分析

  2. メモリ効率の最適化:

    • int32 vs int64: メモリ使用量が半分

    • カテゴリデータをcategory型にすると大幅な節約

  3. エラーの予防:

    • 数値として扱いたいのにobject型 → 計算エラー

    • 日付として扱いたいのにstring型 → 時系列分析不可

# タイプの確認
print(df.dtypes)

欠損値の確認#

欠損値(Missing Values)は,適切に処理しないと,結果が大きく歪む可能性があります.

欠損値が研究に与える影響#

  1. 統計的バイアス: 欠損が偶然でない場合,結果が偏る

  2. サンプルサイズの減少: 分析に使えるデータが減る

  3. 手法の制限: 多くの機械学習アルゴリズムは欠損値を扱えない

欠損値の対処法#

  • 完全削除: 欠損があるレコードを除外(最も安全だが,データ損失大)

  • 平均値補完: 数値データを平均値で埋める(簡単だが,分散が小さくなる)

  • 前方補完/後方補完: 時系列データで前後の値を使用

# 欠損値の確認
print(df.isnull().sum())

時系列データの扱い方#

時系列データは,時間の経過とともに変化するデータのことで,IoT(気温,湿度など),Web解析(アクセス数など),金融(株価など),医療(心拍数など)といったあらゆる分野で活用されています.

なぜ特別な扱いが必要なのか?#

  1. 時間順序の意味: データの順番に意味がある

  2. 季節性・周期性: 曜日,月,季節による規則的な変動パターン

  3. トレンド: 長期的な増加・減少傾向

  4. 自己相関: 過去の値が現在の値に影響する

文字列 → datetime型への変換の重要性#

「2024-01-15」のままでは,ただの文字であり,時間的な計算ができない.datetime型に変換することで,プログラムが実際の日時として認識し,曜日抽出,期間計算,時系列分析が可能になる.

# 文字列から日時型に変換する前のタイプの確認
print("変換前のタイプ: " + str(df["Timestamp"].dtype))

# 文字列から日時型に変換
df['Timestamp'] = pd.to_datetime(df['Timestamp'])

# 文字列から日時型に変換後のタイプの確認
print("変換後のタイプ: " + str(df["Timestamp"].dtype))

時系列から新しい特徴量を生成する#

  • 曜日: 人間の行動パターンは曜日に大きく依存

  • 時間: 日内変動(朝昼夜)のパターン分析

  • 日付: トレンド分析,特定日の影響評価

print("--- 追加前のデータ ---")
print(df.head())

# 時系列データから新しい特徴量を抽出・追加
df['Date']        = df['Timestamp'].dt.date        # 日付
df['Hour']        = df['Timestamp'].dt.hour        # 時間
df['DayOfWeek']   = df['Timestamp'].dt.dayofweek   # 曜日(0:月曜日 ~ 6:日曜日)
df['WeekdayName'] = df['Timestamp'].dt.day_name()  # 曜日名

print("\n--- 追加後のデータ ---")
print(df.head())

基本的な統計情報の取得#

統計情報は,詳細な分析に入る前に必ず確認すべき重要な項目です.

describe()で一気に把握する重要指標#

各統計量の意味:

  1. count (件数): サンプルサイズ

  2. mean (平均): データの中心傾向

  3. std (標準偏差): データのばらつき

  4. min/max: 最小・最大値

  5. 25%/50%/75% (四分位数): データ分布の形状

# 基本的な統計情報をまとめて取得(デフォルトは数値列のみ)
print(df.describe())
# 基本的な統計情報を個別に取得(例:Customer列)
print("件数: " + str(df["Customers"].count()))
print("平均: " + str(df["Customers"].mean()))
print("最小値: " + str(df["Customers"].min()))
print("第1四分位数: " + str(df["Customers"].quantile(0.25)))
print("中央値(第2四分位数): " + str(df["Customers"].median()))
print("第3四分位数: " + str(df["Customers"].quantile(0.75)))
print("最大値: " + str(df["Customers"].max()))
print("標準偏差: " + str(df["Customers"].std()))

データのフィルタリングとソート#

フィルタリングとソートは,膨大なデータから一部を抽出し,パターンを見つける際に使用します.

フィルタリングの研究における重要性#

  1. ノイズ除去: 分析に不要なデータを除外

  2. 条件別分析: 特定の条件下での現象を調査

  3. 異常値除去: 測定エラーや外れ値を排除

  4. サブグループ分析: 性別,年齢層,時間帯などによる層別分析

営業時間フィルタリング#

  • 目的: 店舗が実際に営業している時間のデータのみ分析

  • 応用: 実験時間内のデータ,授業時間内の学習データなど

  • 重要性: 全データを使うと,夜間(客数=0)が平均を下げて誤った結論に

# 営業時間(7時-21時)のデータをフィルタリング
business_hours = df[(df['Hour'] >= 7) & (df['Hour'] <= 21)]
print("営業時間内のデータ割合(%): " + str(len(business_hours) / len(df) * 100))

ソート(並び替え)#

  • 上位・下位の特定: 最も効果的・非効果的な条件を発見

  • ランキング作成: 重要度順,効果順の評価

  • パターン発見: データを並び替えることで隠れた傾向を発見

# 客数が多い順にソート
sorted_df = df.sort_values(by='Customers', ascending=False)
print("客数が多いランキング(上位5件):")
print(sorted_df.head())
# 特定の曜日(土曜日)のデータを抽出
saturday_data = df[df['DayOfWeek'] == 5]  # 5 = 土曜日
print("土曜日のデータ割合(%): " + str(len(saturday_data) / len(df) * 100))
print("土曜日の平均客数(人):", saturday_data['Customers'].mean())
# 複数条件でのフィルタリング例(週末のランチタイム)
weekend_lunch = df[
    (df['DayOfWeek'].isin([5, 6])) &         # 土日
    (df['Hour'] >= 11) & (df['Hour'] <= 14)  # ランチタイム
]
print("週末ランチタイムのデータ割合(%): " + str(len(weekend_lunch) / len(df) * 100))
print("週末ランチタイムの平均客数(人):", weekend_lunch['Customers'].mean())

グループ化と集約#

グループ化(groupby)は,「全体の傾向」ではなく「グループ別の特徴」を理解することで,より深い洞察が得られます.

なぜグループ化が重要なのか?#

単純集計 vs グループ別集計の違い:

  • 単純: 「全体の平均客数は50人」→ 表面的な情報

  • グループ別: 「平日昼12時の平均客数は80人,深夜3時は5人」→ 具体的な行動指針

時間帯別分析 (groupby('Hour'))#

  • 目的: 日内変動パターンの把握

  • 応用例: 学習効率の時間変動,システム負荷の時間分析

  • 価値: ピーク時間の特定,リソース配分の最適化

# 時間帯別の平均客数を計算
hourly_avg = df.groupby('Hour')['Customers'].mean()
print(hourly_avg)

曜日別分析 (groupby('WeekdayName'))#

  • 目的: 週次サイクルの理解

  • 応用例: ユーザー行動の曜日差,実験参加者の曜日による違い

  • 価値: 曜日効果の定量化,スケジューリングの最適化

# 曜日別の平均客数
daily_avg = df.groupby('WeekdayName')['Customers'].mean()
print(daily_avg)

日次集計 (groupby('Date'))#

  • 目的: トレンド分析,長期変動の把握

  • 応用例: 売上推移,学習進捗,システム利用量の変化

  • 価値: 成長率の測定,異常日の検出

# 日付別の合計客数(日次集計)
daily_total = df.groupby('Date')['Customers'].sum()
print(daily_total.head())

groupby(['WeekdayName', 'Hour'])#

  • 意味: 「月曜日の9時」「金曜日の18時」など,具体的なシーン分析

  • 価値: 単一軸では見えない複雑なパターンを可視化

  • 研究応用: 実験条件の組み合わせ効果,多要因分析

unstack()メソッド#

ピボットテーブル形式への変換:

  • 縦長データ → 横長データ(Excel的な見やすさ)

  • 行:時間,列:曜日 → 一目で全パターンを比較可能

  • 研究での使用: 論文の表作成,プレゼン資料の作成

# 時間×曜日のクロス統計
cross_stats = df.groupby(['WeekdayName', 'Hour'])['Customers'].mean().unstack(level=0)
print(cross_stats.head())

インデックスの操作#

インデックス操作は,特に時系列データ分析において極めて重要なスキルです.適切なインデックス設定により,データの抽出・操作が簡単になります.

なぜTimestampをインデックスにするのか?#

メリット:

  1. 日付ベースのスライシング: 「2024年1月のデータ」を簡単抽出

  2. リサンプリング機能: 時間・日・月単位での自動集約

  3. 時系列プロット: matplotlib/seabornでの時系列グラフが美しく描画

  4. 期間計算: 「初日から7日間」のような相対的期間指定

# データフレームのコピーを作成
df_indexed = df.copy()
# インデックス操作前のデータを表示
print("--- 追加前のデータ ---")
print(df_indexed.head())

# Timestampをインデックスに設定
df_indexed = df_indexed.set_index('Timestamp')

print("\n--- 追加後のデータ ---")
print(df_indexed.head())

日付範囲での絞り込み#

  • 実験期間中のデータのみ抽出

  • 特定イベント前後の比較分析

  • 季節・月別の傾向分析

# 利用可能な日付範囲を確認
print(f"開始日: {df_indexed.index.min().date()}")
print(f"終了日: {df_indexed.index.max().date()}")
# 初日のデータを抽出
first_timestamp = df_indexed.index.min() # 最初のタイムスタンプ
first_date      = first_timestamp.date() # 最初の日付

# 初日の全データを取得
print(df_indexed[df_indexed.index.date == first_date])
# 初週(最初の7日間)のデータを抽出
end_date   = first_date + timedelta(days=6) # 最初の日付から6日後まで
first_week = df_indexed[(df_indexed.index.date >= first_date) & (df_indexed.index.date <= end_date)]

リサンプリング#

  • ‘H’: 1時間ごと → 時間別パターン分析

  • ‘D’: 1日ごと → 日次トレンド分析

  • ‘W’: 1週間ごと → 週次変動の把握

  • ‘M’: 1月ごと → 長期トレンド分析

# リサンプリング(1時間ごとの客数合計)
hourly_data = df_indexed.resample('H')['Customers'].sum()

# 1時間ごとの客数(最初の24時間)を表示
print(hourly_data.head(24))

演習問題#

演習1: 営業時間外のデータを除外して、1時間ごとの平均客数を計算してください#

💡ヒント:

  1. 営業時間(7:00-21:00)でフィルタリング

  2. Timestampをインデックスに設定

  3. resample(‘H’)で1時間ごとに集約

# 回答欄

演習2: 平日と週末それぞれの時間帯別平均客数を比較してください#

# 回答欄