データサイエンス with Python

NumPy、Pandas、機械学習

データサイエンス

データサイエンス入門

  • データから洞察を得る 学際的分野
  • Python はデータサイエンスの標準言語
  • 豊富なライブラリエコシステム
  • NumPy - 数値計算の基盤
  • Pandas - データ操作・分析
  • Matplotlib/Seaborn - データ可視化
  • Scikit-learn - 機械学習
# データサイエンスの典型的ワークフロー
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 1. データ収集・読み込み
# 2. データクリーニング・前処理
# 3. 探索的データ分析(EDA)
# 4. モデリング・機械学習
# 5. 結果の可視化・解釈

📱 ナビゲーションのコツ

RevealJS スライドでのスクロール

長いスライドコンテンツの場合:

  • デスクトップ: マウスホイールまたは Page Up/Down キーでスライド内をスクロール
  • モバイル/タッチ: 上下スワイプでスクロール、左右スワイプでスライド移動
  • 矢印キー: ↑↓でスライド内スクロール、←→でスライド間移動
  • キーボードショートカット:
    • ESC - 概要モード
    • F - フルスクリーン
    • S - 発表者モード

NumPy - 数値計算の基盤

import numpy as np

# 配列作成
arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.array([[1, 2, 3], [4, 5, 6]])

print(f"1次元配列: {arr1}")           # [1 2 3 4 5]
print(f"2次元配列:\n{arr2}")          # [[1 2 3], [4 5 6]]
print(f"形状: {arr2.shape}")          # (2, 3)
print(f"データ型: {arr1.dtype}")      # int64

# 便利な配列作成
zeros = np.zeros((3, 4))              # ゼロ配列
ones = np.ones((2, 3))                # 1で埋めた配列
range_arr = np.arange(0, 10, 2)       # [0 2 4 6 8]
linspace = np.linspace(0, 1, 5)       # [0.   0.25 0.5  0.75 1.  ]
random_arr = np.random.rand(3, 3)     # ランダム配列

# 配列演算(ベクトル化)
arr = np.array([1, 2, 3, 4, 5])
print(arr * 2)                        # [2 4 6 8 10]
print(arr ** 2)                       # [1 4 9 16 25]
print(np.sqrt(arr))                   # [1. 1.41 1.73 2. 2.24]

NumPy配列操作と統計

# サンプルデータ
data = np.random.randn(1000)  # 標準正規分布から1000個

# 基本統計
print(f"平均: {np.mean(data):.3f}")
print(f"標準偏差: {np.std(data):.3f}")
print(f"最小値: {np.min(data):.3f}")
print(f"最大値: {np.max(data):.3f}")
print(f"中央値: {np.median(data):.3f}")

# 配列の形状変更
matrix = np.arange(12).reshape(3, 4)
print(f"元の形状: {matrix.shape}")
print(f"転置: {matrix.T.shape}")

# インデックシング・スライシング
print(f"最初の行: {matrix[0]}")
print(f"最後の列: {matrix[:, -1]}")
print(f"条件フィルタ: {matrix[matrix > 5]}")

# 配列の結合
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
combined = np.concatenate([arr1, arr2])  # [1 2 3 4 5 6]
stacked = np.stack([arr1, arr2])         # [[1 2 3], [4 5 6]]

# 線形代数
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(f"行列積: \n{np.dot(A, B)}")
print(f"逆行列: \n{np.linalg.inv(A)}")
print(f"固有値: {np.linalg.eigvals(A)}")

Pandas - データ操作の王様

import pandas as pd

# Series(1次元データ)
series = pd.Series([1, 3, 5, 7, 9], index=['a', 'b', 'c', 'd', 'e'])
print(series)

# DataFrame(2次元データ)
data = {
    '名前': ['太郎', '花子', '次郎', '美香'],
    '年齢': [25, 30, 35, 28],
    '職業': ['エンジニア', 'デザイナー', 'マネージャー', 'データサイエンティスト'],
    '給与': [5000000, 4500000, 7000000, 6000000]
}

df = pd.DataFrame(data)
print(df)
    名前  年齢         職業      給与
0   太郎  25       エンジニア  5000000
1   花子  30      デザイナー  4500000  
2   次郎  35     マネージャー  7000000
3   美香  28  データサイエンティスト  6000000

Pandasでのデータ探索

# データの概要
print(df.info())                    # データ型と欠損値情報
print(df.describe())               # 基本統計量
print(df.head())                   # 最初の5行
print(df.tail(3))                  # 最後の3行

# データ選択
print(df['名前'])                  # 列選択
print(df[['名前', '給与']])        # 複数列選択
print(df.iloc[0])                  # 行選択(位置)
print(df.loc[df['年齢'] > 30])     # 条件選択

# データフィルタリング
高給与者 = df[df['給与'] > 5000000]
若い従業員 = df[df['年齢'] < 30]
エンジニア = df[df['職業'].str.contains('エンジニア')]

# データソート
年齢順 = df.sort_values('年齢')
給与降順 = df.sort_values('給与', ascending=False)

# グループ化と集約
職業別統計 = df.groupby('職業').agg({
    '年齢': 'mean',
    '給与': ['mean', 'max', 'min']
})

# 新しい列の追加
df['年収(万円)'] = df['給与'] / 10000
df['年齢グループ'] = pd.cut(df['年齢'], bins=[0, 30, 40, 100], 
                       labels=['若手', '中堅', 'ベテラン'])

データの読み込みと保存

# CSVファイルの読み込み
# df = pd.read_csv('data.csv', encoding='utf-8')

# 様々なファイル形式をサポート
# df = pd.read_excel('data.xlsx')
# df = pd.read_json('data.json')
# df = pd.read_sql('SELECT * FROM table', connection)

# サンプルデータ作成
import pandas as pd
import numpy as np

# 売上データのサンプル
np.random.seed(42)
dates = pd.date_range('2023-01-01', '2023-12-31', freq='D')
products = ['商品A', '商品B', '商品C', '商品D']

sales_data = []
for date in dates:
    for product in products:
        sales = np.random.poisson(lam=50) + np.random.randint(0, 100)
        price = np.random.uniform(1000, 5000)
        sales_data.append({
            '日付': date,
            '商品': product,
            '販売数': sales,
            '単価': round(price),
            '売上': sales * round(price)
        })

sales_df = pd.DataFrame(sales_data)

# データ保存
sales_df.to_csv('sales_data.csv', index=False, encoding='utf-8')
print("サンプルデータを保存しました")
print(sales_df.head())

データ可視化 - Matplotlib

import matplotlib.pyplot as plt
plt.style.use('default')  # スタイル設定
plt.rcParams['font.family'] = 'DejaVu Sans'  # 日本語対応

# 基本的なプロット
fig, axes = plt.subplots(2, 2, figsize=(12, 8))

# 線グラフ
x = np.linspace(0, 10, 100)
y = np.sin(x)
axes[0, 0].plot(x, y, 'b-', linewidth=2)
axes[0, 0].set_title('Sine Wave')
axes[0, 0].grid(True)

# 散布図
x_scatter = np.random.randn(100)
y_scatter = 2 * x_scatter + np.random.randn(100)
axes[0, 1].scatter(x_scatter, y_scatter, alpha=0.6)
axes[0, 1].set_title('Scatter Plot')

# ヒストグラム
data_hist = np.random.normal(0, 1, 1000)
axes[1, 0].hist(data_hist, bins=30, alpha=0.7, color='green')
axes[1, 0].set_title('Histogram')

# 棒グラフ
categories = ['A', 'B', 'C', 'D']
values = [23, 17, 35, 29]
axes[1, 1].bar(categories, values, color='orange')
axes[1, 1].set_title('Bar Chart')

plt.tight_layout()
plt.show()

# より高度な可視化
plt.figure(figsize=(10, 6))

# 複数系列のプロット
months = ['1月', '2月', '3月', '4月', '5月', '6月']
product_a = [120, 135, 158, 142, 167, 183]
product_b = [98, 112, 126, 139, 145, 156]

plt.plot(months, product_a, 'o-', label='商品A', linewidth=2, markersize=8)
plt.plot(months, product_b, 's-', label='商品B', linewidth=2, markersize=8)
plt.xlabel('月')
plt.ylabel('売上(万円)')
plt.title('月別売上推移')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

データ分析実践例

# 売上データの分析例
def analyze_sales_data():
    # データ読み込み(前回作成したデータ)
    df = sales_df.copy()
    
    # 日付列をdatetime型に変換
    df['日付'] = pd.to_datetime(df['日付'])
    df['月'] = df['日付'].dt.month
    df['曜日'] = df['日付'].dt.day_name()
    
    # 基本統計
    print("=== 基本統計 ===")
    print(f"総売上: {df['売上'].sum():,}円")
    print(f"平均日次売上: {df.groupby('日付')['売上'].sum().mean():,.0f}円")
    print(f"最高日次売上: {df.groupby('日付')['売上'].sum().max():,}円")
    
    # 商品別分析
    print("\n=== 商品別分析 ===")
    product_analysis = df.groupby('商品').agg({
        '売上': ['sum', 'mean'],
        '販売数': 'sum',
        '単価': 'mean'
    }).round(0)
    print(product_analysis)
    
    # 月別分析
    monthly_sales = df.groupby('月')['売上'].sum()
    
    # 曜日別分析
    weekday_sales = df.groupby('曜日')['売上'].sum()
    
    # 相関分析
    correlation = df[['販売数', '単価', '売上']].corr()
    print("\n=== 相関分析 ===")
    print(correlation)
    
    # 可視化
    fig, axes = plt.subplots(2, 2, figsize=(15, 10))
    
    # 商品別売上
    product_total = df.groupby('商品')['売上'].sum()
    axes[0, 0].pie(product_total.values, labels=product_total.index, autopct='%1.1f%%')
    axes[0, 0].set_title('商品別売上割合')
    
    # 月別売上推移
    axes[0, 1].plot(monthly_sales.index, monthly_sales.values, 'o-', linewidth=2)
    axes[0, 1].set_title('月別売上推移')
    axes[0, 1].set_xlabel('月')
    axes[0, 1].set_ylabel('売上(円)')
    axes[0, 1].grid(True)
    
    # 販売数vs単価の散布図
    axes[1, 0].scatter(df['販売数'], df['単価'], alpha=0.6)
    axes[1, 0].set_title('販売数 vs 単価')
    axes[1, 0].set_xlabel('販売数')
    axes[1, 0].set_ylabel('単価(円)')
    
    # 曜日別売上
    weekday_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
    weekday_sales_ordered = weekday_sales.reindex(weekday_order)
    axes[1, 1].bar(range(7), weekday_sales_ordered.values)
    axes[1, 1].set_title('曜日別売上')
    axes[1, 1].set_xlabel('曜日')
    axes[1, 1].set_ylabel('売上(円)')
    axes[1, 1].set_xticks(range(7))
    axes[1, 1].set_xticklabels(['月', '火', '水', '木', '金', '土', '日'])
    
    plt.tight_layout()
    plt.show()
    
    return df

# 分析実行
analyzed_df = analyze_sales_data()

機械学習入門 - Scikit-learn

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np

# サンプルデータ生成(住宅価格予測)
np.random.seed(42)
n_samples = 1000

# 特徴量: 面積、築年数、駅からの距離、部屋数
area = np.random.uniform(30, 150, n_samples)
age = np.random.uniform(0, 30, n_samples)
station_distance = np.random.uniform(1, 20, n_samples)
rooms = np.random.randint(1, 6, n_samples)

# 価格(目的変数): 面積と部屋数は正の影響、築年数と駅距離は負の影響
price = (area * 30 + rooms * 500 - age * 20 - station_distance * 50 + 
         np.random.normal(0, 300, n_samples))
price = np.maximum(price, 1000)  # 最低価格を設定

# DataFrameに整理
housing_data = pd.DataFrame({
    '面積': area,
    '築年数': age,
    '駅距離': station_distance,
    '部屋数': rooms,
    '価格': price
})

print("住宅データサンプル:")
print(housing_data.head())
print(f"\nデータ形状: {housing_data.shape}")
print("\n基本統計:")
print(housing_data.describe())

機械学習モデルの訓練と評価

# 特徴量と目的変数の分離
X = housing_data[['面積', '築年数', '駅距離', '部屋数']]
y = housing_data['価格']

# 訓練・テストデータの分割
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

print(f"訓練データ: {X_train.shape[0]}件")
print(f"テストデータ: {X_test.shape[0]}件")

# モデル1: 線形回帰
lr_model = LinearRegression()
lr_model.fit(X_train, y_train)

# モデル2: ランダムフォレスト
rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)

# 予測
lr_pred = lr_model.predict(X_test)
rf_pred = rf_model.predict(X_test)

# 評価
def evaluate_model(y_true, y_pred, model_name):
    mse = mean_squared_error(y_true, y_pred)
    rmse = np.sqrt(mse)
    r2 = r2_score(y_true, y_pred)
    
    print(f"\n=== {model_name} ===")
    print(f"RMSE: {rmse:.2f}")
    print(f"R²スコア: {r2:.4f}")
    return rmse, r2

lr_rmse, lr_r2 = evaluate_model(y_test, lr_pred, "線形回帰")
rf_rmse, rf_r2 = evaluate_model(y_test, rf_pred, "ランダムフォレスト")

# 特徴量重要度(ランダムフォレスト)
feature_importance = pd.DataFrame({
    '特徴量': X.columns,
    '重要度': rf_model.feature_importances_
}).sort_values('重要度', ascending=False)

print("\n特徴量重要度:")
print(feature_importance)

# 結果の可視化
fig, axes = plt.subplots(1, 3, figsize=(18, 5))

# 実際 vs 予測(線形回帰)
axes[0].scatter(y_test, lr_pred, alpha=0.6)
axes[0].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', lw=2)
axes[0].set_xlabel('実際の価格')
axes[0].set_ylabel('予測価格')
axes[0].set_title(f'線形回帰 (R² = {lr_r2:.3f})')

# 実際 vs 予測(ランダムフォレスト)
axes[1].scatter(y_test, rf_pred, alpha=0.6)
axes[1].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', lw=2)
axes[1].set_xlabel('実際の価格')
axes[1].set_ylabel('予測価格')
axes[1].set_title(f'ランダムフォレスト (R² = {rf_r2:.3f})')

# 特徴量重要度
axes[2].barh(feature_importance['特徴量'], feature_importance['重要度'])
axes[2].set_xlabel('重要度')
axes[2].set_title('特徴量重要度')

plt.tight_layout()
plt.show()

# 新しいデータでの予測例
new_house = pd.DataFrame({
    '面積': [80],
    '築年数': [5],
    '駅距離': [8],
    '部屋数': [3]
})

lr_prediction = lr_model.predict(new_house)[0]
rf_prediction = rf_model.predict(new_house)[0]

print(f"\n新しい住宅の予測価格:")
print(f"面積: {new_house['面積'].iloc[0]}㎡, 築年数: {new_house['築年数'].iloc[0]}年, 駅距離: {new_house['駅距離'].iloc[0]}分, 部屋数: {new_house['部屋数'].iloc[0]}室")
print(f"線形回帰予測: {lr_prediction:,.0f}万円")
print(f"ランダムフォレスト予測: {rf_prediction:,.0f}万円")

実践演習

演習: 顧客セグメンテーション

from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# 顧客データの生成
np.random.seed(42)
n_customers = 500

# 顧客の特徴量
age = np.random.normal(40, 12, n_customers)
income = np.random.normal(500, 150, n_customers)  # 年収(万円)
spending = np.random.normal(300, 100, n_customers)  # 年間支出(万円)
frequency = np.random.poisson(12, n_customers)  # 年間購入回数

# 異常値の調整
age = np.clip(age, 18, 80)
income = np.clip(income, 200, 1000)
spending = np.clip(spending, 50, 800)

customer_data = pd.DataFrame({
    '年齢': age,
    '年収': income,
    '年間支出': spending,
    '購入回数': frequency
})

print("顧客データサンプル:")
print(customer_data.head())
print("\n基本統計:")
print(customer_data.describe())

# データの標準化
scaler = StandardScaler()
scaled_data = scaler.fit_transform(customer_data)

# K-means クラスタリング
kmeans = KMeans(n_clusters=4, random_state=42)
clusters = kmeans.fit_predict(scaled_data)

# クラスタ結果をデータに追加
customer_data['クラスタ'] = clusters

# クラスタ別分析
cluster_analysis = customer_data.groupby('クラスタ').agg({
    '年齢': 'mean',
    '年収': 'mean',
    '年間支出': 'mean',
    '購入回数': 'mean'
}).round(1)

print("\nクラスタ別分析:")
print(cluster_analysis)

# クラスタのラベル付け
cluster_labels = {
    0: "若年層・高頻度",
    1: "高所得者",
    2: "シニア層",
    3: "価格重視層"
}

customer_data['クラスタ名'] = customer_data['クラスタ'].map(cluster_labels)

# 可視化
fig, axes = plt.subplots(2, 2, figsize=(15, 12))

# 年収 vs 年間支出
scatter = axes[0, 0].scatter(customer_data['年収'], customer_data['年間支出'], 
                           c=customer_data['クラスタ'], cmap='viridis', alpha=0.7)
axes[0, 0].set_xlabel('年収(万円)')
axes[0, 0].set_ylabel('年間支出(万円)')
axes[0, 0].set_title('年収 vs 年間支出')
plt.colorbar(scatter, ax=axes[0, 0])

# 年齢 vs 購入回数
scatter2 = axes[0, 1].scatter(customer_data['年齢'], customer_data['購入回数'], 
                            c=customer_data['クラスタ'], cmap='viridis', alpha=0.7)
axes[0, 1].set_xlabel('年齢')
axes[0, 1].set_ylabel('購入回数')
axes[0, 1].set_title('年齢 vs 購入回数')

# クラスタ別分布
cluster_counts = customer_data['クラスタ名'].value_counts()
axes[1, 0].pie(cluster_counts.values, labels=cluster_counts.index, autopct='%1.1f%%')
axes[1, 0].set_title('クラスタ別顧客分布')

# クラスタ別平均値比較
cluster_means = customer_data.groupby('クラスタ名')[['年収', '年間支出']].mean()
cluster_means.plot(kind='bar', ax=axes[1, 1])
axes[1, 1].set_title('クラスタ別平均年収・支出')
axes[1, 1].set_ylabel('金額(万円)')
axes[1, 1].tick_params(axis='x', rotation=45)

plt.tight_layout()
plt.show()

# マーケティング戦略の提案
print("\n=== マーケティング戦略提案 ===")
for cluster_id, label in cluster_labels.items():
    cluster_data = customer_data[customer_data['クラスタ'] == cluster_id]
    size = len(cluster_data)
    avg_income = cluster_data['年収'].mean()
    avg_spending = cluster_data['年間支出'].mean()
    avg_frequency = cluster_data['購入回数'].mean()
    
    print(f"\n{label} ({size}人, {size/len(customer_data)*100:.1f}%)")
    print(f"  平均年収: {avg_income:.0f}万円")
    print(f"  平均支出: {avg_spending:.0f}万円")
    print(f"  平均購入回数: {avg_frequency:.1f}回")
    
    # 戦略提案
    if cluster_id == 0:
        print("  戦略: デジタルマーケティング、頻繁な限定セール")
    elif cluster_id == 1:
        print("  戦略: プレミアム商品、VIPサービス")
    elif cluster_id == 2:
        print("  戦略: 健康・ライフスタイル商品、丁寧なサポート")
    else:
        print("  戦略: 価格競争力、お得なバンドル商品")

重要ポイント

  1. NumPy - 高速数値計算、配列操作の基盤
  2. Pandas - データ操作・分析の必須ツール
  3. Matplotlib - 基本的な可視化ライブラリ
  4. Scikit-learn - 機械学習の標準ライブラリ
  5. 探索的データ分析 - 仮説検証前の重要なステップ

次のステップ

  • Seaborn - より美しい統計グラフ
  • Plotly - インタラクティブな可視化
  • TensorFlow/PyTorch - ディープラーニング
  • Jupyter Notebook - データ分析環境
  • 実データプロジェクト - Kaggleコンペティション

ありがとうございました!

質問はありますか?

データサイエンスの世界へようこそ! 🔬📊

リソース: - NumPy ドキュメント - Pandas ドキュメント - Scikit-learn ドキュメント - Kaggle - データサイエンスコンペティション

ナビゲーション