数据处理与图像绘制应用案例:基于Python的城市气温分析与可视化

1. 项目概述

本项目将通过Python编程语言,结合多个数据处理和可视化库,对城市气温数据进行全面分析和可视化展示。我们将使用真实世界的气象数据,演示从数据获取、清洗、分析到可视化的完整流程。

1.1 项目目标

收集并处理多城市历史气温数据

分析气温变化趋势和季节性模式

创建多种可视化图表展示分析结果

构建交互式数据可视化应用

1.2 技术栈

数据处理:Pandas, NumPy

数据可视化:Matplotlib, Seaborn, Plotly

地理可视化:Folium, Geopandas

交互界面:Streamlit

数据源:公开气象数据集

2. 数据处理流程

下面是完整的数据处理与可视化流程图:

graph TD
    A[数据收集] --> B[数据清洗]
    B --> C[数据转换]
    C --> D[数据分析]
    D --> E[静态可视化]
    D --> F[交互式可视化]
    E --> G[报告生成]
    F --> H[Web应用]
    G --> I[结果展示]
    H --> I
    
    B --> B1[处理缺失值]
    B --> B2[异常值检测]
    B --> B3[数据标准化]
    
    C --> C1[日期时间转换]
    C --> C2[数据聚合]
    C --> C3[特征工程]
    
    D --> D1[描述性统计]
    D --> D2[趋势分析]
    D --> D3[季节性分析]
    
    E --> E1[折线图]
    E --> E2[热力图]
    E --> E3[分布图]
    
    F --> F1[交互式时间序列]
    F --> F2[地理分布图]
    F --> F3[动态仪表板]

数据处理与图像绘制应用案例:基于Python的城市气温分析与可视化

3. 数据收集与预处理

3.1 数据获取

我们首先创建一个模拟多个城市气温数据集的函数,模拟真实世界的气象数据:

python

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import folium
from folium.plugins import HeatMap
import streamlit as st
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

def generate_weather_data():
    """
    生成模拟的多城市气温数据
    """
    np.random.seed(42)
    
    # 城市列表及其基本气候特征
    cities = {
        '北京': {'lat': 39.9, 'lon': 116.4, 'temp_range': (-10, 35)},
        '上海': {'lat': 31.2, 'lon': 121.5, 'temp_range': (0, 38)},
        '广州': {'lat': 23.1, 'lon': 113.3, 'temp_range': (10, 40)},
        '哈尔滨': {'lat': 45.8, 'lon': 126.6, 'temp_range': (-25, 30)},
        '昆明': {'lat': 25.0, 'lon': 102.7, 'temp_range': (5, 28)},
        '乌鲁木齐': {'lat': 43.8, 'lon': 87.6, 'temp_range': (-20, 35)},
        '成都': {'lat': 30.7, 'lon': 104.1, 'temp_range': (5, 35)},
        '西安': {'lat': 34.3, 'lon': 108.9, 'temp_range': (-5, 38)}
    }
    
    # 生成3年的每日数据
    start_date = datetime(2020, 1, 1)
    end_date = datetime(2022, 12, 31)
    date_range = pd.date_range(start=start_date, end=end_date, freq='D')
    
    data = []
    for city, info in cities.items():
        base_temp = (info['temp_range'][0] + info['temp_range'][1]) / 2
        temp_variation = (info['temp_range'][1] - info['temp_range'][0]) / 2
        
        for date in date_range:
            # 模拟季节性变化
            day_of_year = date.timetuple().tm_yday
            seasonal_factor = -np.cos(2 * np.pi * day_of_year / 365) * temp_variation
            
            # 添加随机波动
            random_noise = np.random.normal(0, 3)
            
            # 计算当日温度
            daily_temp = base_temp + seasonal_factor + random_noise
            
            # 添加一些缺失值(模拟真实数据)
            if np.random.random() < 0.01:  # 1%的数据缺失
                daily_temp = np.nan
            
            data.append({
                'city': city,
                'date': date,
                'temperature': daily_temp,
                'latitude': info['lat'],
                'longitude': info['lon']
            })
    
    df = pd.DataFrame(data)
    
    # 添加月份和季节信息
    df['month'] = df['date'].dt.month
    df['year'] = df['date'].dt.year
    df['season'] = df['month'].map({12: '冬季', 1: '冬季', 2: '冬季',
                                   3: '春季', 4: '春季', 5: '春季',
                                   6: '夏季', 7: '夏季', 8: '夏季',
                                   9: '秋季', 10: '秋季', 11: '秋季'})
    
    return df

# 生成数据
weather_df = generate_weather_data()
print("数据生成完成!")
print(f"数据集形状: {weather_df.shape}")
print(weather_df.head())

3.2 数据清洗与探索

接下来我们对数据进行清洗和初步探索:

python

def clean_and_explore_data(df):
    """
    数据清洗与探索性分析
    """
    print("=== 数据清洗与探索性分析 ===")
    
    # 1. 检查数据基本信息
    print("
1. 数据基本信息:")
    print(f"数据形状: {df.shape}")
    print(f"时间范围: {df['date'].min()} 到 {df['date'].max()}")
    print(f"城市数量: {df['city'].nunique()}")
    print(f"包含城市: {', '.join(df['city'].unique())}")
    
    # 2. 检查缺失值
    print("
2. 缺失值统计:")
    missing_data = df.isnull().sum()
    print(missing_data)
    
    # 3. 处理缺失值 - 使用前后值的平均值填充
    df_cleaned = df.copy()
    df_cleaned['temperature'] = df_cleaned.groupby('city')['temperature'].transform(
        lambda x: x.fillna(x.rolling(window=7, min_periods=1).mean())
    )
    
    # 如果还有缺失值,使用该城市的月平均温度填充
    monthly_avg = df_cleaned.groupby(['city', 'month'])['temperature'].transform('mean')
    df_cleaned['temperature'] = df_cleaned['temperature'].fillna(monthly_avg)
    
    print(f"
处理缺失值后,剩余缺失值: {df_cleaned['temperature'].isnull().sum()}")
    
    # 4. 检查异常值
    print("
3. 温度数据统计描述:")
    print(df_cleaned['temperature'].describe())
    
    # 使用箱线图法则检测异常值
    Q1 = df_cleaned['temperature'].quantile(0.25)
    Q3 = df_cleaned['temperature'].quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    
    outliers = df_cleaned[(df_cleaned['temperature'] < lower_bound) | 
                         (df_cleaned['temperature'] > upper_bound)]
    print(f"
检测到的异常值数量: {len(outliers)}")
    
    # 5. 数据分布可视化
    plt.figure(figsize=(15, 10))
    
    plt.subplot(2, 2, 1)
    df_cleaned['temperature'].hist(bins=50, alpha=0.7, color='skyblue')
    plt.title('温度分布直方图')
    plt.xlabel('温度(°C)')
    plt.ylabel('频次')
    
    plt.subplot(2, 2, 2)
    df_cleaned.boxplot(column='temperature', by='city', grid=False)
    plt.title('各城市温度箱线图')
    plt.suptitle('')  # 移除自动标题
    plt.xticks(rotation=45)
    
    plt.subplot(2, 2, 3)
    seasonal_avg = df_cleaned.groupby('season')['temperature'].mean()
    seasonal_avg.plot(kind='bar', color=['lightblue', 'lightgreen', 'lightcoral', 'wheat'])
    plt.title('各季节平均温度')
    plt.ylabel('平均温度(°C)')
    
    plt.subplot(2, 2, 4)
    yearly_avg = df_cleaned.groupby('year')['temperature'].mean()
    yearly_avg.plot(kind='line', marker='o', color='purple')
    plt.title('年度平均温度变化')
    plt.ylabel('平均温度(°C)')
    
    plt.tight_layout()
    plt.savefig('data_exploration.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    return df_cleaned

# 执行数据清洗与探索
cleaned_df = clean_and_explore_data(weather_df)

4. 数据分析与可视化

4.1 气温趋势分析

python

def analyze_temperature_trends(df):
    """
    分析气温趋势
    """
    print("=== 气温趋势分析 ===")
    
    # 1. 计算月度平均温度
    monthly_avg = df.groupby(['year', 'month', 'city'])['temperature'].mean().reset_index()
    monthly_avg['year_month'] = monthly_avg['year'].astype(str) + '-' + monthly_avg['month'].astype(str).str.zfill(2)
    
    # 2. 创建趋势可视化
    fig = make_subplots(
        rows=2, cols=2,
        subplot_titles=('各城市月度温度变化', '年度温度对比', 
                       '季节温度分布', '温度变化趋势'),
        specs=[[{"secondary_y": False}, {"secondary_y": False}],
               [{"secondary_y": False}, {"secondary_y": False}]]
    )
    
    # 各城市月度温度变化(以北京为例)
    beijing_data = monthly_avg[monthly_avg['city'] == '北京']
    fig.add_trace(
        go.Scatter(x=beijing_data['year_month'], y=beijing_data['temperature'],
                  mode='lines+markers', name='北京', line=dict(color='red')),
        row=1, col=1
    )
    
    # 添加其他城市(为了图表清晰,只显示部分)
    for city in ['上海', '广州', '哈尔滨']:
        city_data = monthly_avg[monthly_avg['city'] == city]
        fig.add_trace(
            go.Scatter(x=city_data['year_month'], y=city_data['temperature'],
                      mode='lines', name=city),
            row=1, col=1
        )
    
    # 年度温度对比
    yearly_avg = df.groupby(['year', 'city'])['temperature'].mean().reset_index()
    for city in df['city'].unique():
        city_year_data = yearly_avg[yearly_avg['city'] == city]
        fig.add_trace(
            go.Bar(x=city_year_data['year'], y=city_year_data['temperature'],
                  name=city, showlegend=False),
            row=1, col=2
        )
    
    # 季节温度分布
    seasonal_data = df.groupby(['season', 'city'])['temperature'].mean().reset_index()
    seasons = ['春季', '夏季', '秋季', '冬季']
    
    for i, season in enumerate(seasons):
        season_city_data = seasonal_data[seasonal_data['season'] == season]
        fig.add_trace(
            go.Box(y=season_city_data['temperature'], name=season,
                  marker_color=['lightblue', 'lightgreen', 'lightcoral', 'wheat'][i]),
            row=2, col=1
        )
    
    # 温度变化趋势(线性回归)
    from sklearn.linear_model import LinearRegression
    
    trend_data = []
    for city in df['city'].unique():
        city_data = df[df['city'] == city].copy()
        city_data['day_num'] = (city_data['date'] - city_data['date'].min()).dt.days
        
        X = city_data[['day_num']]
        y = city_data['temperature']
        
        model = LinearRegression()
        model.fit(X, y)
        trend = model.coef_[0] * 365  # 年变化率
        
        trend_data.append({
            'city': city,
            'trend': trend,
            'lat': city_data['latitude'].iloc[0],
            'lon': city_data['longitude'].iloc[0]
        })
    
    trend_df = pd.DataFrame(trend_data)
    
    fig.add_trace(
        go.Bar(x=trend_df['city'], y=trend_df['trend'],
              marker_color=['red' if x > 0 else 'blue' for x in trend_df['trend']]),
        row=2, col=2
    )
    
    fig.update_layout(height=800, title_text="气温趋势综合分析", showlegend=True)
    fig.write_html('temperature_trends.html')
    fig.show()
    
    return trend_df

# 执行趋势分析
trend_analysis = analyze_temperature_trends(cleaned_df)

4.2 气温地理分布可视化

python

def create_geographical_visualizations(df, trend_df):
    """
    创建地理分布可视化
    """
    print("=== 创建地理分布可视化 ===")
    
    # 1. 创建基础地图
    center_lat = df['latitude'].mean()
    center_lon = df['longitude'].mean()
    
    # 平均温度地图
    avg_temp_by_city = df.groupby('city').agg({
        'temperature': 'mean',
        'latitude': 'first',
        'longitude': 'first'
    }).reset_index()
    
    m = folium.Map(location=[center_lat, center_lon], zoom_start=4)
    
    # 添加温度圆圈标记
    for idx, row in avg_temp_by_city.iterrows():
        # 根据温度设置颜色
        temp = row['temperature']
        if temp < 10:
            color = 'blue'
        elif temp < 20:
            color = 'green'
        elif temp < 25:
            color = 'orange'
        else:
            color = 'red'
        
        # 添加圆形标记
        folium.CircleMarker(
            location=[row['latitude'], row['longitude']],
            radius=15,
            popup=f"{row['city']}<br>平均温度: {temp:.1f}°C",
            tooltip=row['city'],
            color=color,
            fillColor=color,
            fillOpacity=0.6
        ).add_to(m)
    
    # 保存地图
    m.save('temperature_map.html')
    
    # 2. 使用Plotly创建交互式地理散点图
    fig = px.scatter_mapbox(avg_temp_by_city,
                           lat="latitude",
                           lon="longitude",
                           hover_name="city",
                           hover_data={"temperature": ":.1f"},
                           color="temperature",
                           size_max=15,
                           zoom=3,
                           mapbox_style="open-street-map",
                           title="各城市平均温度分布",
                           color_continuous_scale=px.colors.sequential.Viridis)
    
    fig.write_html('interactive_temperature_map.html')
    fig.show()
    
    # 3. 创建温度变化趋势地图
    fig_trend = px.scatter_mapbox(trend_df,
                                lat="lat",
                                lon="lon",
                                hover_name="city",
                                hover_data={"trend": ":.3f"},
                                color="trend",
                                color_continuous_scale=px.colors.diverging.RdBu_r,
                                size_max=15,
                                zoom=3,
                                mapbox_style="open-street-map",
                                title="各城市年温度变化趋势(°C/年)")
    
    fig_trend.write_html('temperature_trend_map.html')
    fig_trend.show()
    
    # 4. 创建热力图(需要更多数据点)
    # 为演示目的,我们生成更密集的模拟数据
    heatmap_data = []
    for city, info in avg_temp_by_city.iterrows():
        for _ in range(50):  # 为每个城市生成多个数据点
            lat_noise = np.random.normal(0, 0.5)
            lon_noise = np.random.normal(0, 0.5)
            temp_noise = np.random.normal(0, 2)
            
            heatmap_data.append([
                info['latitude'] + lat_noise,
                info['longitude'] + lon_noise,
                info['temperature'] + temp_noise
            ])
    
    heatmap_df = pd.DataFrame(heatmap_data, columns=['lat', 'lon', 'temperature'])
    
    heatmap_fig = px.density_mapbox(heatmap_df,
                                   lat='lat',
                                   lon='lon',
                                   z='temperature',
                                   radius=10,
                                   center=dict(lat=center_lat, lon=center_lon),
                                   zoom=3,
                                   mapbox_style="open-street-map",
                                   title="温度分布热力图",
                                   color_continuous_scale=px.colors.sequential.Jet)
    
    heatmap_fig.write_html('temperature_heatmap.html')
    heatmap_fig.show()

# 创建地理可视化
create_geographical_visualizations(cleaned_df, trend_analysis)

4.3 高级时间序列分析

python

def advanced_time_series_analysis(df):
    """
    高级时间序列分析
    """
    print("=== 高级时间序列分析 ===")
    
    # 1. 准备数据 - 以北京为例
    beijing_df = df[df['city'] == '北京'].copy()
    beijing_df = beijing_df.set_index('date')
    daily_temp = beijing_df['temperature']
    
    # 2. 移动平均平滑
    daily_temp_7d = daily_temp.rolling(window=7).mean()
    daily_temp_30d = daily_temp.rolling(window=30).mean()
    
    # 3. 季节性分解
    from statsmodels.tsa.seasonal import seasonal_decompose
    
    # 使用月度数据进行分析
    monthly_data = df[df['city'] == '北京'].groupby(pd.Grouper(key='date', freq='M'))['temperature'].mean()
    
    # 季节性分解
    decomposition = seasonal_decompose(monthly_data, model='additive', period=12)
    
    # 4. 创建综合时间序列分析图
    fig = make_subplots(
        rows=3, cols=2,
        subplot_titles=('北京每日温度与移动平均', '温度分布箱线图',
                       '季节性分解 - 趋势', '季节性分解 - 季节性',
                       '年度温度对比', '残差分析'),
        specs=[[{"colspan": 2}, None],
               [{}, {}],
               [{}, {}]]
    )
    
    # 子图1: 每日温度与移动平均
    fig.add_trace(
        go.Scatter(x=daily_temp.index, y=daily_temp, 
                  mode='lines', name='每日温度', line=dict(color='lightgray', width=1)),
        row=1, col=1
    )
    fig.add_trace(
        go.Scatter(x=daily_temp_7d.index, y=daily_temp_7d,
                  mode='lines', name='7日移动平均', line=dict(color='blue', width=2)),
        row=1, col=1
    )
    fig.add_trace(
        go.Scatter(x=daily_temp_30d.index, y=daily_temp_30d,
                  mode='lines', name='30日移动平均', line=dict(color='red', width=2)),
        row=1, col=1
    )
    
    # 子图2: 月度温度分布箱线图
    monthly_box = []
    months = range(1, 13)
    month_names = ['1月', '2月', '3月', '4月', '5月', '6月', 
                  '7月', '8月', '9月', '10月', '11月', '12月']
    
    for month in months:
        monthly_data = beijing_df[beijing_df.index.month == month]['temperature']
        fig.add_trace(
            go.Box(y=monthly_data, name=month_names[month-1], showlegend=False),
            row=1, col=2
        )
    
    # 子图3: 趋势成分
    fig.add_trace(
        go.Scatter(x=decomposition.trend.index, y=decomposition.trend,
                  mode='lines', name='趋势', line=dict(color='green')),
        row=2, col=1
    )
    
    # 子图4: 季节性成分
    fig.add_trace(
        go.Scatter(x=decomposition.seasonal.index, y=decomposition.seasonal,
                  mode='lines', name='季节性', line=dict(color='orange')),
        row=2, col=2
    )
    
    # 子图5: 年度温度对比
    years = df['year'].unique()
    for year in years:
        year_data = beijing_df[beijing_df.index.year == year]
        daily_avg = year_data.groupby(year_data.index.dayofyear)['temperature'].mean()
        fig.add_trace(
            go.Scatter(x=daily_avg.index, y=daily_avg,
                      mode='lines', name=str(year)),
            row=3, col=1
        )
    
    # 子图6: 残差
    fig.add_trace(
        go.Scatter(x=decomposition.resid.index, y=decomposition.resid,
                  mode='markers', name='残差', marker=dict(color='purple', size=4)),
        row=3, col=2
    )
    
    fig.update_layout(height=1200, title_text="北京气温时间序列深度分析", showlegend=True)
    fig.write_html('time_series_analysis.html')
    fig.show()
    
    # 5. 温度极端事件分析
    print("
温度极端事件分析:")
    
    # 计算各月的温度阈值(用于定义极端温度)
    monthly_thresholds = beijing_df.groupby(beijing_df.index.month)['temperature'].agg([
        ('Q1', lambda x: x.quantile(0.05)),  # 极端低温阈值(5%分位数)
        ('Q3', lambda x: x.quantile(0.95))   # 极端高温阈值(95%分位数)
    ]).reset_index()
    
    # 识别极端温度日
    extreme_days = []
    for idx, row in beijing_df.iterrows():
        month = idx.month
        threshold = monthly_thresholds[monthly_thresholds['index'] == month]
        
        if row['temperature'] < threshold['Q1'].values[0]:
            extreme_days.append({'date': idx, 'temperature': row['temperature'], 
                               'type': '极端低温', 'deviation': row['temperature'] - threshold['Q1'].values[0]})
        elif row['temperature'] > threshold['Q3'].values[0]:
            extreme_days.append({'date': idx, 'temperature': row['temperature'], 
                               'type': '极端高温', 'deviation': row['temperature'] - threshold['Q3'].values[0]})
    
    extreme_df = pd.DataFrame(extreme_days)
    
    if not extreme_df.empty:
        print(f"检测到极端温度天数: {len(extreme_df)}")
        print(f"极端高温天数: {len(extreme_df[extreme_df['type'] == '极端高温'])}")
        print(f"极端低温天数: {len(extreme_df[extreme_df['type'] == '极端低温'])}")
        
        # 极端事件可视化
        plt.figure(figsize=(12, 6))
        
        # 绘制温度时间序列并标记极端事件
        plt.plot(beijing_df.index, beijing_df['temperature'], 
                alpha=0.5, color='gray', label='每日温度')
        
        hot_events = extreme_df[extreme_df['type'] == '极端高温']
        cold_events = extreme_df[extreme_df['type'] == '极端低温']
        
        plt.scatter(hot_events['date'], hot_events['temperature'], 
                   color='red', s=30, label='极端高温', alpha=0.7)
        plt.scatter(cold_events['date'], cold_events['temperature'], 
                   color='blue', s=30, label='极端低温', alpha=0.7)
        
        plt.title('北京极端温度事件检测')
        plt.ylabel('温度(°C)')
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.tight_layout()
        plt.savefig('extreme_events.png', dpi=300, bbox_inches='tight')
        plt.show()
    
    return extreme_df

# 执行高级时间序列分析
extreme_events = advanced_time_series_analysis(cleaned_df)

5. 交互式数据可视化应用

5.1 创建Streamlit交互式应用

python

def create_interactive_dashboard(df):
    """
    创建交互式仪表板
    """
    import streamlit as st
    import plotly.express as px
    import plotly.graph_objects as go
    
    st.set_page_config(
        page_title="城市气温分析仪表板",
        page_icon="🌡️",
        layout="wide",
        initial_sidebar_state="expanded"
    )
    
    st.title("🌡️ 中国主要城市气温数据分析仪表板")
    st.markdown("---")
    
    # 侧边栏控件
    st.sidebar.header("数据筛选选项")
    
    # 城市选择
    available_cities = df['city'].unique()
    selected_cities = st.sidebar.multiselect(
        "选择城市",
        options=available_cities,
        default=['北京', '上海', '广州']
    )
    
    # 时间范围选择
    min_date = df['date'].min()
    max_date = df['date'].max()
    
    date_range = st.sidebar.date_input(
        "选择时间范围",
        value=(min_date, max_date),
        min_value=min_date,
        max_value=max_date
    )
    
    # 数据聚合级别
    aggregation = st.sidebar.selectbox(
        "数据聚合级别",
        options=["每日", "每周", "月度", "年度"]
    )
    
    # 根据选择过滤数据
    filtered_df = df[df['city'].isin(selected_cities)]
    
    if len(date_range) == 2:
        start_date, end_date = date_range
        filtered_df = filtered_df[
            (filtered_df['date'] >= pd.Timestamp(start_date)) & 
            (filtered_df['date'] <= pd.Timestamp(end_date))
        ]
    
    # 根据聚合级别重新采样数据
    if aggregation == "每周":
        agg_df = filtered_df.groupby(['city', pd.Grouper(key='date', freq='W')])['temperature'].mean().reset_index()
    elif aggregation == "月度":
        agg_df = filtered_df.groupby(['city', pd.Grouper(key='date', freq='M')])['temperature'].mean().reset_index()
    elif aggregation == "年度":
        agg_df = filtered_df.groupby(['city', pd.Grouper(key='date', freq='Y')])['temperature'].mean().reset_index()
    else:
        agg_df = filtered_df
    
    # 主仪表板布局
    col1, col2 = st.columns([2, 1])
    
    with col1:
        st.subheader("温度时间序列")
        
        if not agg_df.empty:
            fig = px.line(agg_df, x='date', y='temperature', color='city',
                         title=f"各城市温度变化趋势 ({aggregation})",
                         labels={'temperature': '温度 (°C)', 'date': '日期'})
            
            fig.update_layout(
                hovermode='x unified',
                xaxis=dict(rangeslider=dict(visible=True))
            )
            
            st.plotly_chart(fig, use_container_width=True)
        else:
            st.warning("请选择至少一个城市")
    
    with col2:
        st.subheader("数据统计")
        
        if not filtered_df.empty:
            # 计算基本统计量
            stats_data = []
            for city in selected_cities:
                city_data = filtered_df[filtered_df['city'] == city]['temperature']
                stats_data.append({
                    '城市': city,
                    '平均温度': f"{city_data.mean():.1f}°C",
                    '最高温度': f"{city_data.max():.1f}°C",
                    '最低温度': f"{city_data.min():.1f}°C",
                    '温度标准差': f"{city_data.std():.1f}°C"
                })
            
            stats_df = pd.DataFrame(stats_data)
            st.dataframe(stats_df, use_container_width=True)
            
            # 季节性平均温度
            st.subheader("季节性分析")
            seasonal_avg = filtered_df.groupby(['city', 'season'])['temperature'].mean().reset_index()
            
            fig_season = px.box(seasonal_avg, x='season', y='temperature', color='city',
                               title="各城市季节性温度分布")
            st.plotly_chart(fig_season, use_container_width=True)
    
    # 第二行:地理分布和热力图
    col3, col4 = st.columns(2)
    
    with col3:
        st.subheader("地理分布")
        
        # 计算各城市平均温度
        city_avg = filtered_df.groupby('city').agg({
            'temperature': 'mean',
            'latitude': 'first',
            'longitude': 'first'
        }).reset_index()
        
        if not city_avg.empty:
            fig_map = px.scatter_mapbox(city_avg,
                                       lat="latitude",
                                       lon="longitude",
                                       hover_name="city",
                                       hover_data={"temperature": ":.1f"},
                                       color="temperature",
                                       size_max=15,
                                       zoom=3,
                                       mapbox_style="open-street-map",
                                       title="城市平均温度分布")
            
            st.plotly_chart(fig_map, use_container_width=True)
    
    with col4:
        st.subheader("温度分布直方图")
        
        if not filtered_df.empty:
            fig_hist = px.histogram(filtered_df, x='temperature', color='city',
                                   marginal="box", nbins=50,
                                   title="温度分布直方图",
                                   labels={'temperature': '温度 (°C)'})
            
            st.plotly_chart(fig_hist, use_container_width=True)
    
    # 第三行:高级分析
    st.subheader("高级分析")
    
    col5, col6 = st.columns(2)
    
    with col5:
        # 月度温度热力图
        st.markdown("##### 月度温度热力图")
        
        if not filtered_df.empty:
            # 创建数据透视表
            monthly_pivot = filtered_df.pivot_table(
                values='temperature',
                index='city',
                columns='month',
                aggfunc='mean'
            )
            
            fig_heatmap = px.imshow(monthly_pivot,
                                   aspect="auto",
                                   title="各城市月度平均温度热力图",
                                   color_continuous_scale="Viridis",
                                   labels=dict(x="月份", y="城市", color="温度 (°C)"))
            
            st.plotly_chart(fig_heatmap, use_container_width=True)
    
    with col6:
        # 年度对比
        st.markdown("##### 年度温度对比")
        
        if not filtered_df.empty:
            yearly_avg = filtered_df.groupby(['city', 'year'])['temperature'].mean().reset_index()
            
            fig_year = px.line(yearly_avg, x='year', y='temperature', color='city',
                              markers=True, title="年度平均温度变化")
            
            st.plotly_chart(fig_year, use_container_width=True)
    
    # 数据下载
    st.sidebar.markdown("---")
    st.sidebar.subheader("数据导出")
    
    if st.sidebar.button("导出筛选数据为CSV"):
        csv = filtered_df.to_csv(index=False)
        st.sidebar.download_button(
            label="下载CSV文件",
            data=csv,
            file_name=f"temperature_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
            mime="text/csv"
        )

# 注意:Streamlit应用需要单独运行
# 保存为单独的文件并运行: streamlit run app.py

5.2 创建完整的Streamlit应用文件

将以下代码保存为 
temperature_dashboard.py
 并运行 
streamlit run temperature_dashboard.py

python

# temperature_dashboard.py
import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')

# 设置页面
st.set_page_config(
    page_title="城市气温分析仪表板",
    page_icon="🌡️",
    layout="wide",
    initial_sidebar_state="expanded"
)

# 标题
st.title("🌡️ 中国主要城市气温数据分析仪表板")
st.markdown("""
本仪表板展示中国主要城市的历史气温数据,提供多种可视化方式分析温度趋势、分布和模式。
""")
st.markdown("---")

# 生成示例数据(在实际应用中,这里应该从文件或数据库加载数据)
@st.cache_data
def load_data():
    """生成示例气温数据"""
    np.random.seed(42)
    
    cities = {
        '北京': {'lat': 39.9, 'lon': 116.4, 'temp_range': (-10, 35)},
        '上海': {'lat': 31.2, 'lon': 121.5, 'temp_range': (0, 38)},
        '广州': {'lat': 23.1, 'lon': 113.3, 'temp_range': (10, 40)},
        '哈尔滨': {'lat': 45.8, 'lon': 126.6, 'temp_range': (-25, 30)},
        '昆明': {'lat': 25.0, 'lon': 102.7, 'temp_range': (5, 28)},
        '成都': {'lat': 30.7, 'lon': 104.1, 'temp_range': (5, 35)},
    }
    
    date_range = pd.date_range(start='2020-01-01', end='2022-12-31', freq='D')
    
    data = []
    for city, info in cities.items():
        base_temp = (info['temp_range'][0] + info['temp_range'][1]) / 2
        temp_variation = (info['temp_range'][1] - info['temp_range'][0]) / 2
        
        for date in date_range:
            day_of_year = date.timetuple().tm_yday
            seasonal_factor = -np.cos(2 * np.pi * day_of_year / 365) * temp_variation
            random_noise = np.random.normal(0, 3)
            
            daily_temp = base_temp + seasonal_factor + random_noise
            
            data.append({
                'city': city,
                'date': date,
                'temperature': daily_temp,
                'latitude': info['lat'],
                'longitude': info['lon']
            })
    
    df = pd.DataFrame(data)
    df['month'] = df['date'].dt.month
    df['year'] = df['date'].dt.year
    df['season'] = df['month'].map({12: '冬季', 1: '冬季', 2: '冬季',
                                   3: '春季', 4: '春季', 5: '春季',
                                   6: '夏季', 7: '夏季', 8: '夏季',
                                   9: '秋季', 10: '秋季', 11: '秋季'})
    return df

# 加载数据
df = load_data()

# 侧边栏
st.sidebar.header("🔧 数据筛选选项")

# 城市选择
available_cities = df['city'].unique()
selected_cities = st.sidebar.multiselect(
    "选择城市",
    options=available_cities,
    default=['北京', '上海', '广州']
)

# 时间范围选择
min_date = df['date'].min().date()
max_date = df['date'].max().date()

date_range = st.sidebar.date_input(
    "选择时间范围",
    value=(min_date, max_date),
    min_value=min_date,
    max_value=max_date
)

# 数据聚合级别
aggregation = st.sidebar.selectbox(
    "数据聚合级别",
    options=["每日", "每周", "月度", "年度"]
)

# 根据选择过滤数据
filtered_df = df[df['city'].isin(selected_cities)]

if len(date_range) == 2:
    start_date, end_date = date_range
    filtered_df = filtered_df[
        (filtered_df['date'] >= pd.Timestamp(start_date)) & 
        (filtered_df['date'] <= pd.Timestamp(end_date))
    ]

# 根据聚合级别重新采样数据
if aggregation == "每周":
    agg_df = filtered_df.groupby(['city', pd.Grouper(key='date', freq='W')])['temperature'].mean().reset_index()
    agg_df['date'] = agg_df['date'] - pd.Timedelta(days=6)  # 调整到周开始
elif aggregation == "月度":
    agg_df = filtered_df.groupby(['city', pd.Grouper(key='date', freq='M')])['temperature'].mean().reset_index()
elif aggregation == "年度":
    agg_df = filtered_df.groupby(['city', pd.Grouper(key='date', freq='Y')])['temperature'].mean().reset_index()
else:
    agg_df = filtered_df

# 主仪表板
if not selected_cities:
    st.warning("⚠️ 请至少选择一个城市")
else:
    # 第一行:关键指标
    st.subheader("📊 关键指标")
    
    col1, col2, col3, col4 = st.columns(4)
    
    with col1:
        avg_temp = filtered_df['temperature'].mean()
        st.metric("平均温度", f"{avg_temp:.1f}°C")
    
    with col2:
        max_temp = filtered_df['temperature'].max()
        st.metric("最高温度", f"{max_temp:.1f}°C")
    
    with col3:
        min_temp = filtered_df['temperature'].min()
        st.metric("最低温度", f"{min_temp:.1f}°C")
    
    with col4:
        temp_std = filtered_df['temperature'].std()
        st.metric("温度标准差", f"{temp_std:.1f}°C")
    
    # 第二行:时间序列和统计
    col1, col2 = st.columns([2, 1])
    
    with col1:
        st.subheader("📈 温度时间序列")
        
        fig = px.line(agg_df, x='date', y='temperature', color='city',
                     title=f"各城市温度变化趋势 ({aggregation})",
                     labels={'temperature': '温度 (°C)', 'date': '日期'})
        
        fig.update_layout(
            hovermode='x unified',
            xaxis=dict(rangeslider=dict(visible=True))
        )
        
        st.plotly_chart(fig, use_container_width=True)
    
    with col2:
        st.subheader("📋 数据统计")
        
        # 计算基本统计量
        stats_data = []
        for city in selected_cities:
            city_data = filtered_df[filtered_df['city'] == city]['temperature']
            stats_data.append({
                '城市': city,
                '平均温度': f"{city_data.mean():.1f}°C",
                '最高温度': f"{city_data.max():.1f}°C",
                '最低温度': f"{city_data.min():.1f}°C",
                '温度标准差': f"{city_data.std():.1f}°C"
            })
        
        stats_df = pd.DataFrame(stats_data)
        st.dataframe(stats_df, use_container_width=True)
    
    # 第三行:地理分布和直方图
    col3, col4 = st.columns(2)
    
    with col3:
        st.subheader("🗺️ 地理分布")
        
        # 计算各城市平均温度
        city_avg = filtered_df.groupby('city').agg({
            'temperature': 'mean',
            'latitude': 'first',
            'longitude': 'first'
        }).reset_index()
        
        fig_map = px.scatter_mapbox(city_avg,
                                   lat="latitude",
                                   lon="longitude",
                                   hover_name="city",
                                   hover_data={"temperature": ":.1f"},
                                   color="temperature",
                                   size_max=15,
                                   zoom=3,
                                   mapbox_style="open-street-map",
                                   title="城市平均温度分布")
        
        st.plotly_chart(fig_map, use_container_width=True)
    
    with col4:
        st.subheader("📊 温度分布")
        
        fig_hist = px.histogram(filtered_df, x='temperature', color='city',
                               marginal="box", nbins=50,
                               title="温度分布直方图",
                               labels={'temperature': '温度 (°C)'})
        
        st.plotly_chart(fig_hist, use_container_width=True)
    
    # 第四行:季节性分析和热力图
    st.subheader("🎯 深度分析")
    
    col5, col6 = st.columns(2)
    
    with col5:
        st.markdown("##### 季节性分析")
        
        seasonal_avg = filtered_df.groupby(['city', 'season'])['temperature'].mean().reset_index()
        
        fig_season = px.box(seasonal_avg, x='season', y='temperature', color='city',
                           title="各城市季节性温度分布")
        st.plotly_chart(fig_season, use_container_width=True)
    
    with col6:
        st.markdown("##### 月度温度热力图")
        
        # 创建数据透视表
        monthly_pivot = filtered_df.pivot_table(
            values='temperature',
            index='city',
            columns='month',
            aggfunc='mean'
        )
        
        # 重命名月份列
        month_names = ['1月', '2月', '3月', '4月', '5月', '6月', 
                      '7月', '8月', '9月', '10月', '11月', '12月']
        monthly_pivot.columns = month_names[:len(monthly_pivot.columns)]
        
        fig_heatmap = px.imshow(monthly_pivot,
                               aspect="auto",
                               title="各城市月度平均温度热力图",
                               color_continuous_scale="Viridis",
                               labels=dict(x="月份", y="城市", color="温度 (°C)"))
        
        st.plotly_chart(fig_heatmap, use_container_width=True)

# 侧边栏底部
st.sidebar.markdown("---")
st.sidebar.markdown("### 关于")
st.sidebar.info(
    """
    这是一个演示性的气温数据分析仪表板,使用模拟数据展示数据处理和可视化的能力。
    在实际应用中,可以连接真实的气象数据库或API。
    """
)

6. 项目总结与扩展

6.1 项目成果总结

通过本项目,我们实现了:

完整的数据处理流程:从数据生成、清洗、转换到分析

多维度可视化:静态图表、交互式图表、地理可视化

高级分析功能:趋势分析、季节性分解、极端事件检测

交互式应用:基于Streamlit的完整数据仪表板

6.2 技术亮点

使用Pandas进行高效数据处理

结合Matplotlib/Seaborn创建静态可视化

利用Plotly创建交互式可视化

使用Folium进行地理数据可视化

基于Streamlit构建完整Web应用

实现时间序列分析和统计建模

6.3 扩展可能性

本项目可以进一步扩展:

连接真实数据源:接入气象API或数据库

机器学习预测:添加温度预测功能

实时数据更新:实现实时数据监控

多变量分析:结合湿度、降水等多气象要素

移动端适配:开发移动友好的界面

6.4 部署与运行

要运行此项目:

安装依赖:
pip install pandas numpy matplotlib seaborn plotly folium streamlit

运行Streamlit应用:
streamlit run temperature_dashboard.py

访问本地服务器(通常是 http://localhost:8501)

这个案例展示了如何将数据处理与图像绘制技术结合,创建从基础分析到高级可视化的完整解决方案,为类似的数据分析项目提供了可复用的模板和方法论。

© 版权声明

相关文章

暂无评论

none
暂无评论...