22.数据探索:像侦探一样分析数据

数据探索:像侦探一样分析数据

🕵️ 前言:数据侦探的日常

想象一下,你是一个穿着风衣的侦探,面前摆着一堆乱七八糟的证据。有些证据看起来很重要,有些像是红鲱鱼,还有些藏在角落里不起眼却可能是破案关键。这就是数据探索的真实写照——在一堆数据中寻找真相!

今天我们要学习的数据探索(Exploratory Data Analysis,简称EDA),就像是侦探工作的第一步:仔细观察现场,寻找线索,建立假设。不过别担心,我们不需要放大镜和烟斗,只需要Python和一颗好奇心!

📚 目录

🎯 什么是数据探索

数据探索就像是第一次约会——你需要了解对方的基本情况,看看有没有共同话题,观察一下有没有什么奇怪的地方。只不过,你的约会对象是一堆数字和字符串。

数据探索的核心目标:

  1. 了解数据的基本情况:有多少行多少列,都是什么类型的数据
  2. 发现数据的分布特征:数据是怎么分布的,有没有倾斜
  3. 识别异常值:找出那些"不合群"的数据点
  4. 探索变量间的关系:看看哪些变量是好朋友,哪些是冤家
  5. 为后续分析做准备:为建模和预测打下基础

🔧 侦探的工具箱

首先,让我们准备好侦探工具:

# 导入必要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings('ignore')

# 设置中文字体和图形样式
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
sns.set_style("whitegrid")
sns.set_palette("husl")

# 设置pandas显示选项
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', 100)
pd.set_option('display.width', 1000)

📊 数据的第一印象

就像见到新朋友要先握手问好,我们也要先和数据打个招呼:

# 创建一个示例数据集(电商销售数据)
np.random.seed(42)
n_samples = 1000

# 生成模拟数据
data = {
    'customer_id': range(1, n_samples + 1),
    'age': np.random.normal(35, 12, n_samples).astype(int),
    'gender': np.random.choice(['男', '女'], n_samples),
    'city': np.random.choice(['北京', '上海', '广州', '深圳', '杭州'], n_samples),
    'purchase_amount': np.random.lognormal(6, 0.8, n_samples),
    'purchase_frequency': np.random.poisson(3, n_samples),
    'satisfaction_score': np.random.uniform(1, 5, n_samples),
    'is_vip': np.random.choice([0, 1], n_samples, p=[0.7, 0.3])
}

# 添加一些异常值和缺失值
data['age'][np.random.choice(n_samples, 20)] = np.random.choice([150, -10, 200], 20)
data['purchase_amount'][np.random.choice(n_samples, 30)] = np.nan
data['satisfaction_score'][np.random.choice(n_samples, 50)] = np.nan

df = pd.DataFrame(data)

# 数据探索的第一步:基本信息
print("📋 数据基本信息")
print("=" * 50)
print(f"数据形状: {df.shape}")
print(f"总行数: {len(df)}")
print(f"总列数: {len(df.columns)}")
print("\n列名:")
print(df.columns.tolist())

数据类型侦查

def explore_data_types(df):
    """探索数据类型"""
    print("🔍 数据类型侦查报告")
    print("=" * 50)
    
    type_info = df.dtypes.value_counts()
    print("数据类型分布:")
    for dtype, count in type_info.items():
        print(f"  {dtype}: {count} 列")
    
    print("\n详细信息:")
    for col in df.columns:
        dtype = df[col].dtype
        null_count = df[col].isnull().sum()
        null_pct = (null_count / len(df)) * 100
        unique_count = df[col].nunique()
        
        print(f"  {col:20s} | {str(dtype):10s} | 缺失: {null_count:3d} ({null_pct:5.1f}%) | 唯一值: {unique_count}")

explore_data_types(df)

数据预览

def data_preview(df):
    """数据预览"""
    print("👀 数据预览")
    print("=" * 50)
    
    print("前5行:")
    print(df.head())
    
    print("\n后5行:")
    print(df.tail())
    
    print("\n随机5行:")
    print(df.sample(5))

data_preview(df)

📈 数值变量的秘密

数值变量就像是会说话的数字,我们需要倾听它们的故事:

def analyze_numerical_variables(df):
    """分析数值变量"""
    print("📊 数值变量分析报告")
    print("=" * 50)
    
    # 获取数值列
    numerical_cols = df.select_dtypes(include=[np.number]).columns
    print(f"数值列: {list(numerical_cols)}")
    
    # 描述性统计
    desc = df[numerical_cols].describe()
    print("\n描述性统计:")
    print(desc)
    
    # 检查分布
    fig, axes = plt.subplots(2, 2, figsize=(15, 10))
    axes = axes.ravel()
    
    for i, col in enumerate(numerical_cols[:4]):
        # 直方图
        axes[i].hist(df[col].dropna(), bins=30, alpha=0.7, color='skyblue', edgecolor='black')
        axes[i].set_title(f'{col} 分布')
        axes[i].set_xlabel(col)
        axes[i].set_ylabel('频次')
        
        # 添加统计信息
        mean_val = df[col].mean()
        median_val = df[col].median()
        axes[i].axvline(mean_val, color='red', linestyle='--', label=f'均值: {mean_val:.2f}')
        axes[i].axvline(median_val, color='green', linestyle='--', label=f'中位数: {median_val:.2f}')
        axes[i].legend()
    
    plt.tight_layout()
    plt.show()

analyze_numerical_variables(df)

分布形状诊断

def distribution_diagnosis(df, column):
    """分布形状诊断"""
    print(f"🔬 {column} 分布诊断")
    print("=" * 50)
    
    data = df[column].dropna()
    
    # 基本统计量
    print(f"均值: {data.mean():.2f}")
    print(f"中位数: {data.median():.2f}")
    print(f"标准差: {data.std():.2f}")
    print(f"偏度: {data.skew():.2f}")
    print(f"峰度: {data.kurtosis():.2f}")
    
    # 分布形状判断
    if abs(data.skew()) < 0.5:
        skew_desc = "近似对称"
    elif data.skew() > 0:
        skew_desc = "右偏(长尾在右)"
    else:
        skew_desc = "左偏(长尾在左)"
    
    print(f"分布形状: {skew_desc}")
    
    # 绘制分布图
    fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    
    # 直方图
    axes[0].hist(data, bins=30, alpha=0.7, color='lightblue', edgecolor='black')
    axes[0].set_title(f'{column} 直方图')
    axes[0].set_xlabel(column)
    axes[0].set_ylabel('频次')
    
    # 箱线图
    axes[1].boxplot(data, vert=True)
    axes[1].set_title(f'{column} 箱线图')
    axes[1].set_ylabel(column)
    
    # Q-Q图
    stats.probplot(data, dist="norm", plot=axes[2])
    axes[2].set_title(f'{column} Q-Q图')
    
    plt.tight_layout()
    plt.show()

# 诊断购买金额分布
distribution_diagnosis(df, 'purchase_amount')

📋 分类变量的故事

分类变量就像是给数据贴标签,我们需要看看每个标签有多少人:

def analyze_categorical_variables(df):
    """分析分类变量"""
    print("📋 分类变量分析报告")
    print("=" * 50)
    
    # 获取分类列
    categorical_cols = df.select_dtypes(include=['object']).columns
    print(f"分类列: {list(categorical_cols)}")
    
    fig, axes = plt.subplots(1, len(categorical_cols), figsize=(15, 5))
    if len(categorical_cols) == 1:
        axes = [axes]
    
    for i, col in enumerate(categorical_cols):
        # 计算频次
        value_counts = df[col].value_counts()
        
        print(f"\n{col} 分布:")
        for value, count in value_counts.items():
            percentage = (count / len(df)) * 100
            print(f"  {value}: {count} ({percentage:.1f}%)")
        
        # 绘制条形图
        value_counts.plot(kind='bar', ax=axes[i], color='lightcoral')
        axes[i].set_title(f'{col} 分布')
        axes[i].set_xlabel(col)
        axes[i].set_ylabel('频次')
        axes[i].tick_params(axis='x', rotation=45)
    
    plt.tight_layout()
    plt.show()

analyze_categorical_variables(df)

交叉表分析

def cross_table_analysis(df, var1, var2):
    """交叉表分析"""
    print(f"🔄 {var1} vs {var2} 交叉分析")
    print("=" * 50)
    
    # 创建交叉表
    cross_tab = pd.crosstab(df[var1], df[var2], margins=True)
    print("频次交叉表:")
    print(cross_tab)
    
    # 百分比交叉表
    cross_tab_pct = pd.crosstab(df[var1], df[var2], normalize='index') * 100
    print("\n百分比交叉表:")
    print(cross_tab_pct.round(1))
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(15, 5))
    
    # 热力图
    sns.heatmap(cross_tab.iloc[:-1, :-1], annot=True, fmt='d', ax=axes[0], cmap='Blues')
    axes[0].set_title(f'{var1} vs {var2} 热力图')
    
    # 堆叠条形图
    cross_tab.iloc[:-1, :-1].plot(kind='bar', stacked=True, ax=axes[1])
    axes[1].set_title(f'{var1} vs {var2} 堆叠条形图')
    axes[1].set_xlabel(var1)
    axes[1].set_ylabel('频次')
    axes[1].legend(title=var2)
    
    plt.tight_layout()
    plt.show()

# 分析性别和城市的关系
cross_table_analysis(df, 'gender', 'city')

🔍 寻找数据中的异常

异常值就像是数据中的"奇葩",可能是错误,也可能是重要发现:

def detect_outliers(df, column, method='iqr'):
    """检测异常值"""
    print(f"🚨 {column} 异常值检测")
    print("=" * 50)
    
    data = df[column].dropna()
    
    if method == 'iqr':
        # IQR方法
        Q1 = data.quantile(0.25)
        Q3 = data.quantile(0.75)
        IQR = Q3 - Q1
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR
        
        outliers = data[(data < lower_bound) | (data > upper_bound)]
        
        print(f"IQR方法:")
        print(f"  Q1: {Q1:.2f}")
        print(f"  Q3: {Q3:.2f}")
        print(f"  IQR: {IQR:.2f}")
        print(f"  下界: {lower_bound:.2f}")
        print(f"  上界: {upper_bound:.2f}")
        
    elif method == 'zscore':
        # Z-score方法
        z_scores = np.abs(stats.zscore(data))
        outliers = data[z_scores > 3]
        
        print(f"Z-score方法:")
        print(f"  阈值: 3")
        
    print(f"  异常值数量: {len(outliers)}")
    print(f"  异常值比例: {len(outliers)/len(data)*100:.1f}%")
    
    if len(outliers) > 0:
        print(f"  异常值范围: {outliers.min():.2f} - {outliers.max():.2f}")
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(15, 5))
    
    # 箱线图
    axes[0].boxplot(data, vert=True)
    axes[0].set_title(f'{column} 箱线图')
    axes[0].set_ylabel(column)
    
    # 散点图
    axes[1].scatter(range(len(data)), data, alpha=0.6)
    if method == 'iqr':
        axes[1].axhline(y=lower_bound, color='red', linestyle='--', label='下界')
        axes[1].axhline(y=upper_bound, color='red', linestyle='--', label='上界')
    axes[1].set_title(f'{column} 散点图')
    axes[1].set_xlabel('索引')
    axes[1].set_ylabel(column)
    axes[1].legend()
    
    plt.tight_layout()
    plt.show()
    
    return outliers

# 检测年龄异常值
age_outliers = detect_outliers(df, 'age', method='iqr')
print(f"年龄异常值: {age_outliers.values}")

多变量异常检测

def multivariate_outlier_detection(df, columns):
    """多变量异常检测"""
    print("🎯 多变量异常检测")
    print("=" * 50)
    
    # 选择数值列
    data = df[columns].dropna()
    
    # 计算马氏距离
    from scipy.spatial.distance import mahalanobis
    
    # 计算协方差矩阵的逆
    cov_matrix = np.cov(data.T)
    inv_cov_matrix = np.linalg.inv(cov_matrix)
    
    # 计算每个点的马氏距离
    mean = data.mean()
    mahal_dist = []
    
    for i in range(len(data)):
        point = data.iloc[i]
        dist = mahalanobis(point, mean, inv_cov_matrix)
        mahal_dist.append(dist)
    
    # 找出异常值
    threshold = np.percentile(mahal_dist, 95)  # 95%分位数作为阈值
    outlier_indices = np.where(np.array(mahal_dist) > threshold)[0]
    
    print(f"异常值阈值: {threshold:.2f}")
    print(f"异常值数量: {len(outlier_indices)}")
    print(f"异常值比例: {len(outlier_indices)/len(data)*100:.1f}%")
    
    # 可视化
    plt.figure(figsize=(10, 6))
    plt.scatter(range(len(mahal_dist)), mahal_dist, alpha=0.6)
    plt.axhline(y=threshold, color='red', linestyle='--', label=f'阈值: {threshold:.2f}')
    plt.title('马氏距离分布')
    plt.xlabel('数据点索引')
    plt.ylabel('马氏距离')
    plt.legend()
    plt.show()
    
    return outlier_indices

# 检测年龄和购买金额的多变量异常
outlier_indices = multivariate_outlier_detection(df, ['age', 'purchase_amount'])

💝 变量间的关系

变量间的关系就像是人际关系,有些是好朋友,有些是敌人:

def correlation_analysis(df):
    """相关性分析"""
    print("💕 变量相关性分析")
    print("=" * 50)
    
    # 计算相关性矩阵
    numerical_cols = df.select_dtypes(include=[np.number]).columns
    corr_matrix = df[numerical_cols].corr()
    
    print("相关性矩阵:")
    print(corr_matrix.round(3))
    
    # 寻找强相关关系
    print("\n强相关关系 (|r| > 0.5):")
    strong_corr = []
    for i in range(len(corr_matrix.columns)):
        for j in range(i+1, len(corr_matrix.columns)):
            corr_val = corr_matrix.iloc[i, j]
            if abs(corr_val) > 0.5:
                col1, col2 = corr_matrix.columns[i], corr_matrix.columns[j]
                strong_corr.append((col1, col2, corr_val))
                print(f"  {col1} vs {col2}: {corr_val:.3f}")
    
    # 可视化相关性矩阵
    fig, axes = plt.subplots(1, 2, figsize=(15, 6))
    
    # 热力图
    sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0, ax=axes[0])
    axes[0].set_title('相关性热力图')
    
    # 散点图矩阵 (选择部分变量)
    selected_cols = numerical_cols[:4]  # 选择前4个数值列
    pd.plotting.scatter_matrix(df[selected_cols], alpha=0.6, figsize=(10, 10), ax=axes[1])
    
    plt.tight_layout()
    plt.show()
    
    return strong_corr

strong_correlations = correlation_analysis(df)

分组分析

def group_analysis(df, group_by, analyze_col):
    """分组分析"""
    print(f"👥 按{group_by}分组分析{analyze_col}")
    print("=" * 50)
    
    # 分组统计
    grouped = df.groupby(group_by)[analyze_col].agg([
        'count', 'mean', 'median', 'std', 'min', 'max'
    ]).round(2)
    
    print("分组统计:")
    print(grouped)
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(15, 5))
    
    # 箱线图
    df.boxplot(column=analyze_col, by=group_by, ax=axes[0])
    axes[0].set_title(f'{analyze_col}{group_by} 分组箱线图')
    axes[0].set_xlabel(group_by)
    axes[0].set_ylabel(analyze_col)
    
    # 条形图
    grouped['mean'].plot(kind='bar', ax=axes[1], color='lightgreen')
    axes[1].set_title(f'{analyze_col}{group_by} 分组均值')
    axes[1].set_xlabel(group_by)
    axes[1].set_ylabel(f'{analyze_col} 均值')
    axes[1].tick_params(axis='x', rotation=45)
    
    plt.tight_layout()
    plt.show()
    
    return grouped

# 按性别分析购买金额
gender_analysis = group_analysis(df, 'gender', 'purchase_amount')

🚀 高级侦探技巧

主成分分析(PCA)

def pca_analysis(df, n_components=2):
    """主成分分析"""
    print("🔍 主成分分析")
    print("=" * 50)
    
    from sklearn.decomposition import PCA
    from sklearn.preprocessing import StandardScaler
    
    # 选择数值列
    numerical_cols = df.select_dtypes(include=[np.number]).columns
    data = df[numerical_cols].dropna()
    
    # 标准化
    scaler = StandardScaler()
    data_scaled = scaler.fit_transform(data)
    
    # PCA
    pca = PCA(n_components=n_components)
    pca_result = pca.fit_transform(data_scaled)
    
    # 解释方差比例
    explained_variance_ratio = pca.explained_variance_ratio_
    print(f"前{n_components}个主成分解释的方差比例:")
    for i, ratio in enumerate(explained_variance_ratio):
        print(f"  PC{i+1}: {ratio:.3f} ({ratio*100:.1f}%)")
    
    print(f"累计解释方差比例: {sum(explained_variance_ratio):.3f} ({sum(explained_variance_ratio)*100:.1f}%)")
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(15, 6))
    
    # 散点图
    axes[0].scatter(pca_result[:, 0], pca_result[:, 1], alpha=0.6)
    axes[0].set_xlabel(f'PC1 ({explained_variance_ratio[0]*100:.1f}%)')
    axes[0].set_ylabel(f'PC2 ({explained_variance_ratio[1]*100:.1f}%)')
    axes[0].set_title('PCA 散点图')
    
    # 载荷图
    loadings = pca.components_.T
    for i, (col, loading) in enumerate(zip(numerical_cols, loadings)):
        axes[1].arrow(0, 0, loading[0], loading[1], 
                     head_width=0.05, head_length=0.05, fc='red', ec='red')
        axes[1].text(loading[0]*1.1, loading[1]*1.1, col, fontsize=10)
    
    axes[1].set_xlabel('PC1')
    axes[1].set_ylabel('PC2')
    axes[1].set_title('PCA 载荷图')
    axes[1].grid(True)
    
    plt.tight_layout()
    plt.show()
    
    return pca_result, pca

pca_result, pca_model = pca_analysis(df)

聚类分析

def clustering_analysis(df, n_clusters=3):
    """聚类分析"""
    print("🎯 聚类分析")
    print("=" * 50)
    
    from sklearn.cluster import KMeans
    from sklearn.preprocessing import StandardScaler
    
    # 选择数值列
    numerical_cols = df.select_dtypes(include=[np.number]).columns
    data = df[numerical_cols].dropna()
    
    # 标准化
    scaler = StandardScaler()
    data_scaled = scaler.fit_transform(data)
    
    # K-means聚类
    kmeans = KMeans(n_clusters=n_clusters, random_state=42)
    clusters = kmeans.fit_predict(data_scaled)
    
    # 添加聚类结果到数据框
    data_with_clusters = data.copy()
    data_with_clusters['cluster'] = clusters
    
    # 分析每个聚类
    print("聚类分析结果:")
    for i in range(n_clusters):
        cluster_data = data_with_clusters[data_with_clusters['cluster'] == i]
        print(f"\n聚类 {i} ({len(cluster_data)} 个样本):")
        print(cluster_data.describe().round(2))
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(15, 6))
    
    # 使用前两个主成分进行可视化
    from sklearn.decomposition import PCA
    pca = PCA(n_components=2)
    pca_result = pca.fit_transform(data_scaled)
    
    # 聚类散点图
    colors = ['red', 'blue', 'green', 'orange', 'purple']
    for i in range(n_clusters):
        mask = clusters == i
        axes[0].scatter(pca_result[mask, 0], pca_result[mask, 1], 
                       c=colors[i], label=f'聚类 {i}', alpha=0.6)
    
    axes[0].set_xlabel('PC1')
    axes[0].set_ylabel('PC2')
    axes[0].set_title('聚类结果 (PCA空间)')
    axes[0].legend()
    
    # 聚类中心
    centers_pca = pca.transform(kmeans.cluster_centers_)
    axes[0].scatter(centers_pca[:, 0], centers_pca[:, 1], 
                   c='black', marker='x', s=200, linewidths=3, label='中心')
    
    # 每个特征的聚类对比
    cluster_means = data_with_clusters.groupby('cluster').mean()
    cluster_means.plot(kind='bar', ax=axes[1])
    axes[1].set_title('各聚类特征均值对比')
    axes[1].set_xlabel('聚类')
    axes[1].set_ylabel('特征值')
    axes[1].legend(bbox_to_anchor=(1.05, 1), loc='upper left')
    
    plt.tight_layout()
    plt.show()
    
    return clusters, kmeans

clusters, kmeans_model = clustering_analysis(df)

🎯 实战案例:房价侦探

让我们用一个完整的案例来演示数据探索的全过程:

# 创建房价数据集
def create_housing_dataset():
    """创建房价数据集"""
    np.random.seed(42)
    n_samples = 1000
    
    # 生成特征
    area = np.random.normal(100, 30, n_samples)  # 面积
    rooms = np.random.poisson(3, n_samples) + 1  # 房间数
    age = np.random.exponential(10, n_samples)  # 房龄
    distance = np.random.gamma(2, 2, n_samples)  # 距离市中心距离
    
    # 生成价格(有一定的逻辑关系)
    price = (area * 0.5 + rooms * 10 - age * 0.3 - distance * 2 + 
             np.random.normal(0, 10, n_samples) + 50)
    
    # 确保价格为正
    price = np.maximum(price, 20)
    
    # 添加一些异常值
    outlier_indices = np.random.choice(n_samples, 20, replace=False)
    price[outlier_indices] = price[outlier_indices] * 3
    
    housing_data = pd.DataFrame({
        'area': area,
        'rooms': rooms,
        'age': age,
        'distance_to_center': distance,
        'price': price,
        'district': np.random.choice(['朝阳', '海淀', '西城', '东城'], n_samples),
        'has_elevator': np.random.choice([0, 1], n_samples)
    })
    
    return housing_data

def comprehensive_eda(df):
    """综合数据探索分析"""
    print("🏠 房价数据综合探索分析")
    print("=" * 70)
    
    # 1. 基本信息
    print("1. 基本信息")
    print("-" * 30)
    print(f"数据形状: {df.shape}")
    print(f"数据类型:\n{df.dtypes}")
    print(f"缺失值:\n{df.isnull().sum()}")
    
    # 2. 描述性统计
    print("\n2. 描述性统计")
    print("-" * 30)
    print(df.describe())
    
    # 3. 相关性分析
    print("\n3. 相关性分析")
    print("-" * 30)
    numerical_cols = df.select_dtypes(include=[np.number]).columns
    corr_matrix = df[numerical_cols].corr()
    
    # 找出与价格最相关的特征
    price_corr = corr_matrix['price'].sort_values(ascending=False)
    print("与价格的相关性:")
    for col, corr in price_corr.items():
        if col != 'price':
            print(f"  {col}: {corr:.3f}")
    
    # 4. 可视化
    fig, axes = plt.subplots(2, 3, figsize=(18, 12))
    
    # 价格分布
    axes[0, 0].hist(df['price'], bins=30, alpha=0.7, color='skyblue')
    axes[0, 0].set_title('价格分布')
    axes[0, 0].set_xlabel('价格')
    axes[0, 0].set_ylabel('频次')
    
    # 面积vs价格
    axes[0, 1].scatter(df['area'], df['price'], alpha=0.6)
    axes[0, 1].set_title('面积 vs 价格')
    axes[0, 1].set_xlabel('面积')
    axes[0, 1].set_ylabel('价格')
    
    # 房间数vs价格
    room_price = df.groupby('rooms')['price'].mean()
    axes[0, 2].bar(room_price.index, room_price.values, color='lightgreen')
    axes[0, 2].set_title('房间数 vs 平均价格')
    axes[0, 2].set_xlabel('房间数')
    axes[0, 2].set_ylabel('平均价格')
    
    # 房龄vs价格
    axes[1, 0].scatter(df['age'], df['price'], alpha=0.6, color='coral')
    axes[1, 0].set_title('房龄 vs 价格')
    axes[1, 0].set_xlabel('房龄')
    axes[1, 0].set_ylabel('价格')
    
    # 距离vs价格
    axes[1, 1].scatter(df['distance_to_center'], df['price'], alpha=0.6, color='gold')
    axes[1, 1].set_title('距离市中心 vs 价格')
    axes[1, 1].set_xlabel('距离市中心')
    axes[1, 1].set_ylabel('价格')
    
    # 相关性热力图
    sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0, ax=axes[1, 2])
    axes[1, 2].set_title('相关性热力图')
    
    plt.tight_layout()
    plt.show()
    
    # 5. 区域分析
    print("\n5. 区域分析")
    print("-" * 30)
    district_stats = df.groupby('district')['price'].agg(['count', 'mean', 'median', 'std'])
    print(district_stats.round(2))
    
    # 区域价格分布
    fig, axes = plt.subplots(1, 2, figsize=(15, 5))
    
    df.boxplot(column='price', by='district', ax=axes[0])
    axes[0].set_title('各区域价格分布')
    axes[0].set_xlabel('区域')
    axes[0].set_ylabel('价格')
    
    district_stats['mean'].plot(kind='bar', ax=axes[1], color='lightcoral')
    axes[1].set_title('各区域平均价格')
    axes[1].set_xlabel('区域')
    axes[1].set_ylabel('平均价格')
    axes[1].tick_params(axis='x', rotation=45)
    
    plt.tight_layout()
    plt.show()
    
    # 6. 异常值检测
    print("\n6. 异常值检测")
    print("-" * 30)
    
    # 使用IQR方法检测价格异常值
    Q1 = df['price'].quantile(0.25)
    Q3 = df['price'].quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    
    outliers = df[(df['price'] < lower_bound) | (df['price'] > upper_bound)]
    print(f"价格异常值数量: {len(outliers)}")
    print(f"异常值比例: {len(outliers)/len(df)*100:.1f}%")
    
    if len(outliers) > 0:
        print(f"异常值价格范围: {outliers['price'].min():.2f} - {outliers['price'].max():.2f}")
    
    return {
        'correlation_matrix': corr_matrix,
        'district_stats': district_stats,
        'outliers': outliers
    }

# 创建并分析房价数据
housing_df = create_housing_dataset()
analysis_results = comprehensive_eda(housing_df)

🔧 常见问题与解决方案

Q1: 数据量太大,探索很慢怎么办?

A1: 数据抽样策略

def sample_data_for_eda(df, sample_size=10000):
    """为EDA抽样数据"""
    if len(df) > sample_size:
        # 分层抽样
        if 'category_column' in df.columns:
            sample_df = df.groupby('category_column').apply(
                lambda x: x.sample(min(len(x), sample_size // df['category_column'].nunique()))
            ).reset_index(drop=True)
        else:
            sample_df = df.sample(n=sample_size, random_state=42)
        
        print(f"数据从 {len(df)} 行抽样到 {len(sample_df)} 行")
        return sample_df
    else:
        return df

Q2: 缺失值太多,怎么处理?

A2: 缺失值分析

def analyze_missing_values(df):
    """分析缺失值"""
    missing_stats = df.isnull().sum()
    missing_pct = (missing_stats / len(df)) * 100
    
    missing_df = pd.DataFrame({
        'missing_count': missing_stats,
        'missing_percentage': missing_pct
    })
    
    # 按缺失比例排序
    missing_df = missing_df.sort_values('missing_percentage', ascending=False)
    
    print("缺失值分析:")
    print(missing_df[missing_df['missing_count'] > 0])
    
    # 可视化缺失值模式
    plt.figure(figsize=(10, 6))
    sns.heatmap(df.isnull(), cbar=True, yticklabels=False, cmap='viridis')
    plt.title('缺失值模式')
    plt.show()
    
    return missing_df

Q3: 如何自动化EDA流程?

A3: 自动化EDA函数

def automated_eda(df, target_col=None):
    """自动化EDA"""
    report = {
        'basic_info': {},
        'numerical_analysis': {},
        'categorical_analysis': {},
        'correlation_analysis': {},
        'outlier_analysis': {}
    }
    
    # 基本信息
    report['basic_info'] = {
        'shape': df.shape,
        'dtypes': df.dtypes.to_dict(),
        'missing_values': df.isnull().sum().to_dict()
    }
    
    # 数值变量分析
    numerical_cols = df.select_dtypes(include=[np.number]).columns
    for col in numerical_cols:
        report['numerical_analysis'][col] = {
            'mean': df[col].mean(),
            'median': df[col].median(),
            'std': df[col].std(),
            'skewness': df[col].skew(),
            'kurtosis': df[col].kurtosis()
        }
    
    # 分类变量分析
    categorical_cols = df.select_dtypes(include=['object']).columns
    for col in categorical_cols:
        report['categorical_analysis'][col] = df[col].value_counts().to_dict()
    
    # 相关性分析
    if len(numerical_cols) > 1:
        corr_matrix = df[numerical_cols].corr()
        report['correlation_analysis'] = corr_matrix.to_dict()
    
    # 异常值检测
    for col in numerical_cols:
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR
        
        outliers = df[(df[col] < lower_bound) | (df[col] > upper_bound)]
        report['outlier_analysis'][col] = {
            'outlier_count': len(outliers),
            'outlier_percentage': len(outliers) / len(df) * 100
        }
    
    return report

# 使用自动化EDA
eda_report = automated_eda(housing_df, target_col='price')
print("自动化EDA报告生成完成!")

📖 扩展阅读

推荐工具和库

  • pandas-profiling: 自动生成EDA报告
  • sweetviz: 美观的EDA可视化
  • autoviz: 自动化数据可视化
  • plotly: 交互式图表
  • bokeh: 大数据可视化

学习资源

  • 《Python数据分析实战》
  • 《利用Python进行数据分析》
  • Kaggle Learn 的数据可视化课程
  • 各种数据科学竞赛的EDA kernel

🎬 下集预告

下一期我们将学习《统计分析:从数据中挖掘洞察》,将深入探讨:

  • 假设检验的艺术
  • 置信区间的秘密
  • 回归分析的威力
  • 贝叶斯统计的魅力

📝 总结与思考题

本期总结

  1. 数据探索是数据科学的基础:就像侦探工作一样,需要仔细观察和分析
  2. 掌握多种探索方法:从基本统计到高级分析技术
  3. 可视化是强大工具:图表比数字更直观
  4. 异常值需要特别关注:可能是错误,也可能是重要发现
  5. 变量关系分析很重要:为后续建模奠定基础

思考题

  1. 如果你是一个电商数据分析师,你会如何探索用户购买行为数据?
  2. 在什么情况下异常值应该被保留?什么情况下应该被删除?
  3. 如何设计一个通用的EDA流程,适用于不同类型的数据集?
  4. 相关性高的变量一定有因果关系吗?如何区分相关性和因果性?

实践作业

  1. 找一个真实的数据集,进行完整的EDA分析
  2. 比较不同异常值检测方法的效果
  3. 尝试使用pandas-profiling等自动化工具
  4. 为你的EDA结果写一份数据洞察报告

记住,数据探索就像和数据谈恋爱——需要耐心、细心,还要有一点点好奇心。只有真正了解你的数据,才能从中发现有价值的洞察!🕵️‍♂️✨

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值