代码初始化¶

定义变量及导入数据集¶

In [206]:
# import libraries

# datetime management
import calendar
from datetime import datetime

# data management
import numpy as np
import scipy.stats as stats
from scipy.signal import find_peaks

import pandas as pd
from pandas.plotting import scatter_matrix

# graph plotting
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.font_manager import FontProperties
from matplotlib import rcParams

import seaborn as sn

from IPython.display import display, HTML

# machine learning
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

from sklearn.cluster import DBSCAN
from sklearn.cluster import OPTICS

from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor, AdaBoostRegressor
from sklearn.metrics import mean_squared_error, r2_score

import xgboost as xgb

import lime
import lime.lime_tabular

# warnings (to hide some useless warnings).
import warnings
warnings.filterwarnings('ignore', category=FutureWarning)
warnings.filterwarnings('ignore', category=UserWarning)

# plotly renderer settings.
import plotly.io as pio
pio.renderers.default = 'notebook'

# font settings 
font_path = '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc'
font_prop = FontProperties(fname=font_path)
config = {
                    "font.family":'monospace',
                    "font.monospace": [font_prop.get_name(), 'DejaVu Sans Mono'],    
                    "font.size": 12,             
                    "mathtext.fontset":'stix', 
                    "axes.unicode_minus": False,  
                    "xtick.direction":'in',
                    "ytick.direction":'in',       
                }
rcParams.update(config)

# define dataset
df = pd.read_csv('data.csv', delimiter = ',', header = 0)

# modify dataset
df["date"] = df.datetime.apply(lambda x : x.split()[0])
df["hour"] = df.datetime.apply(lambda x : x.split()[1].split(":")[0]).astype(int)
df["weekday"] = df.date.apply(lambda dateString : calendar.day_name[datetime.strptime(dateString,"%Y-%m-%d").weekday()])
df["month"] = df.date.apply(lambda dateString : calendar.month_name[datetime.strptime(dateString,"%Y-%m-%d").month])
df['month_num'] = df['month'].astype('category').cat.codes
df["year"] = df.date.apply(lambda dateString: int(datetime.strptime(dateString, "%Y-%m-%d").year))
df["day"] = df.date.apply(lambda dateString: int(datetime.strptime(dateString, "%Y-%m-%d").day))

# define variables
datetime = df['datetime']
time = df['datetime'].str.slice(11,13)
season = df['season']
holiday = df['holiday']
workingday = df['workingday']
weather = df['weather']
temp = df['temp']
atemp = df['atemp']
humidity = df['humidity']
windspeed = df['windspeed']
casual = df['casual']
registered = df['registered']
count = df['count']
common = df['count'] - df['registered']

features = ['season', 'holiday', 'workingday', 'weather', 'temp', 'atemp', 'humidity', 'windspeed', 'year', 'month_num', 'day', 'hour']
target = 'count'

X = df[features]
Y = df[target]
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.2, random_state = 42)

models = {
    "RandomForest": RandomForestRegressor(random_state=42),
    "GradientBoosting": GradientBoostingRegressor(random_state=42),
    "AdaBoost": AdaBoostRegressor(random_state=42),
    "LinearRegression": LinearRegression()
}

读取数据¶

读取数据集¶

In [44]:
# read data.
df.head()
Out[44]:
datetime season holiday workingday weather temp atemp humidity windspeed casual registered count date hour weekday month
0 2011-01-01 00:00:00 1 0 0 1 9.84 14.395 81 0.0 3 13 16 2011-01-01 00 Saturday January
1 2011-01-01 01:00:00 1 0 0 1 9.02 13.635 80 0.0 8 32 40 2011-01-01 01 Saturday January
2 2011-01-01 02:00:00 1 0 0 1 9.02 13.635 80 0.0 5 27 32 2011-01-01 02 Saturday January
3 2011-01-01 03:00:00 1 0 0 1 9.84 14.395 75 0.0 3 10 13 2011-01-01 03 Saturday January
4 2011-01-01 04:00:00 1 0 0 1 9.84 14.395 75 0.0 0 1 1 2011-01-01 04 Saturday January

数据集信息¶

In [177]:
# fetch info.
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10886 entries, 0 to 10885
Data columns (total 19 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   datetime    10886 non-null  object 
 1   season      10886 non-null  int64  
 2   holiday     10886 non-null  int64  
 3   workingday  10886 non-null  int64  
 4   weather     10886 non-null  int64  
 5   temp        10886 non-null  float64
 6   atemp       10886 non-null  float64
 7   humidity    10886 non-null  int64  
 8   windspeed   10886 non-null  float64
 9   casual      10886 non-null  int64  
 10  registered  10886 non-null  int64  
 11  count       10886 non-null  int64  
 12  date        10886 non-null  object 
 13  hour        10886 non-null  int64  
 14  weekday     10886 non-null  object 
 15  month       10886 non-null  object 
 16  month_num   10886 non-null  int8   
 17  year        10886 non-null  int64  
 18  day         10886 non-null  int64  
dtypes: float64(3), int64(11), int8(1), object(4)
memory usage: 1.5+ MB

数据预处理¶

用户分布数据处理¶

In [46]:
# organize general data.
general_reg_user = pd.concat([registered, common], axis=1)
general_reg_user = general_reg_user.sum()

# organize average workingday/holiday data.
workingday_user = df.groupby(['workingday']).sum()['count'].reset_index().drop(0)
holiday_user = df.groupby(['holiday']).sum()['count'].reset_index().drop(0)
avrg_data = {'Working Day': [workingday_user['count'].sum()/7412], 'Holiday': [holiday_user['count'].sum()/311]}
avrg_user = pd.DataFrame(avrg_data).sum()

# organize seasonal data
seasonal_counts = df.groupby(['season']).sum()['count'].reset_index()

用户活跃度数据预处理¶

In [47]:
# organize the DataFrame to show the count of different type of users differ from current time of the day.
user_active_time_cmp = pd.concat([time, count, registered, common], axis=1)
user_active_time_cmp_organized = user_active_time_cmp.groupby('datetime').agg({'count': 'sum', 'registered': 'sum', 0: 'sum'}).reset_index()
# by season.
user_active_season_cmp = pd.DataFrame(df.groupby(["hour","season"],sort=True)["count"].mean()).reset_index()
# by month.
sort_order = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
month_aggregated = pd.DataFrame(df.groupby("month")["count"].mean()).reset_index()
month_aggregated['month'] = pd.Categorical(month_aggregated['month'], categories=sort_order, ordered=True)
month_sorted = month_aggregated.sort_values(by='month')
# by week.
hue_order = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday", "Sunday"]
user_active_week_cmp = pd.DataFrame(df.groupby(["hour","weekday"],sort=True)["count"].mean()).reset_index()
user_active_week_cmp['weekday'] = pd.Categorical(user_active_week_cmp['weekday'], categories=hue_order, ordered=True)

hour_transformed = pd.melt(df[["hour", "casual", "registered"]], 
                           id_vars=['hour'], 
                           value_vars=['casual', 'registered'],
                           var_name='user_type', 
                           value_name='user_count')
hour_aggregated = pd.DataFrame(hour_transformed.groupby(["hour", "user_type"], sort=True)["user_count"].mean()).reset_index()
hour_aggregated['total'] = hour_aggregated.groupby('hour')['user_count'].transform('sum')

数据相关性分析¶

这是一个散点矩阵图,用于同时可视化多个变量之间的关系。每个小格子代表两个变量之间的散点图,其中对角线上的小格子表示单个变量的密度图。这张图展示了四个变量:用户数量(count)、风速(windspeed)、温度(temp)和湿度(humidity)。

  • 对角线上:

    • 用户数量密度图:显示了数据分布情况,可以看出用户数量大致呈正态分布,峰值在 500 左右。
    • 风速密度图:风速主要集中在较低值,大部分数据分布在 10m/s 以内。
    • 温度密度图:温度主要分布在 10°C 至 30°C 之间,峰值在 20°C 左右。
    • 湿度密度图:湿度主要分布在 50% 至 80% 之间,峰值在 60% 左右。
  • 上三角形区域:

    • 用户数量 & 风速:两者似乎呈现负相关,随着风速增加,用户数量减少。
    • 用户数量 & 温度:可能存在一定的正相关,较高的温度对应着更多的用户数量。
    • 用户数量 & 湿度:似乎没有明显的相关性。
    • 风速 & 温度:风速和温度之间存在一些负相关性,但不是很强。
    • 风速 & 湿度:风速和湿度之间也没有明显的关系。
    • 温度 & 湿度:温度和湿度之间似乎有一些正相关性。
  • 下三角形区域:

    • 用户数量 & 用户数量:这是对角线的重复,表示单个变量的分布。
    • 风速 & 风速:同理,也是重复的。
    • 温度 & 温度:同样如此。
    • 湿度 & 湿度:再次重复。

总结:用户数量受风速和温度影响较大,湿度对其影响较小。温度和湿度之间有一定相关性。

In [169]:
%config InlineBackend.figure_format = 'retina' 
scatter_matrix(df[['count', 'windspeed', 'temp', 'humidity']], alpha=0.5, figsize=(10, 10), diagonal='kde')
plt.suptitle('变量的散点矩阵', fontsize = 24, fontproperties=font_prop)
plt.tight_layout()
plt.show()

这是一张热力图,显示了不同变量之间的相关性。每个单元格的颜色和数值表示两个变量之间的相关系数,范围从-1到1。以下是各列的详细分析:

temp (温度)¶

  • atemp: 0.98(强正相关)
  • casual: 0.47(中等正相关)
  • registered: 0.32(弱正相关)
  • humidity: -0.065(几乎无相关)
  • windspeed: -0.018(几乎无相关)
  • count: 0.39(弱正相关)

atemp (调整后的温度)¶

  • temp: 0.98(强正相关)
  • casual: 0.46(中等正相关)
  • registered: 0.31(弱正相关)
  • humidity: -0.044(几乎无相关)
  • windspeed: -0.057(几乎无相关)
  • count: 0.39(弱正相关)

casual (群众用户数量)¶

  • temp: 0.47(中等正相关)
  • atemp: 0.46(中等正相关)
  • registered: 0.5(中等正相关)
  • humidity: -0.35(弱负相关)
  • windspeed: 0.092(几乎无相关)
  • count: 0.69(中等正相关)

registered (注册用户数量)¶

  • temp: 0.32(弱正相关)
  • atemp: 0.31(弱正相关)
  • casual: 0.5(中等正相关)
  • humidity: -0.27(弱负相关)
  • windspeed: 0.091(几乎无相关)
  • count: 0.97(强正相关)

humidity (湿度)¶

  • temp: -0.065(几乎无相关)
  • atemp: -0.044(几乎无相关)
  • casual: -0.35(弱负相关)
  • registered: -0.27(弱负相关)
  • windspeed: -0.32(弱负相关)
  • count: -0.32(弱负相关)

windspeed (风速)¶

  • temp: -0.018(几乎无相关)
  • atemp: -0.057(几乎无相关)
  • casual: 0.092(几乎无相关)
  • registered: 0.091(几乎无相关)
  • humidity: -0.32(弱负相关)
  • count: 0.1(几乎无相关)

count (总用户数量)¶

  • temp: 0.39(弱正相关)
  • atemp: 0.39(弱正相关)
  • casual: 0.69(中等正相关)
  • registered: 0.97(强正相关)
  • humidity: -0.32(弱负相关)
  • windspeed: 0.1(几乎无相关)

总结:

  • 温度(temp)和调整后的温度(atemp)之间有很强的相关性。
  • 总用户数量(count)与休闲用户数量(casual)和注册用户数量(registered)有较强的相关性。
  • 湿度(humidity)对所有其他变量都有轻微的负面影响。
  • 风速(windspeed)与其他变量的关系较弱。
In [49]:
%config InlineBackend.figure_format = 'retina' 
corrMatt = df[["temp","atemp","casual","registered","humidity","windspeed","count"]].corr()
mask = np.triu(np.ones_like(corrMatt, dtype=bool))
htmp,ax= plt.subplots()
htmp.set_size_inches(20,10)
sn.heatmap(corrMatt, mask=mask,vmax=.8, square=True,annot=True)
Out[49]:
<Axes: >

用户分布¶

当我们查看饼图时,可以看到以下关键点:

  1. 用户类别分布:
    • 左侧的饼图显示了注册用户和未注册用户的比例。81.2%的用户是注册用户,而18.8%的用户尚未注册。这表明大多数用户已经完成了注册流程,显示出他们对共享自行车服务的偏好和满意度。
  2. 季节性使用习惯:
    • 右侧的饼图揭示了不同季节中用户数量的差异。秋季用户数量最多,占30.7%,其次是夏季,占28.2%。冬季和春季分别占26.1%和15%。这些数据表明,在气候温和的春秋两季,人们更愿意使用共享自行车出行,而冬季和夏季的使用率较低。
  3. 节假日工作日平均用户比例
    • 中间的图显示了工作日平均用户和节假日平均用户的比例。二者差距不大,但在节假日时略多。这些数据表明他们的用户组成很广泛。

这些分析提供了有关用户行为的基本概况。注册用户占比大,说明服务受欢迎程度较高,而季节性使用趋势可以帮助公司优化资源配置和推广策略。

In [50]:
# define subplots.
user_fig = make_subplots(rows=1, cols=3, specs=[[{'type':'domain'}, {'type':'domain'}, {'type':'domain'}]])

# general user distribution.
user_fig.add_trace(go.Pie(hoverinfo='label+percent', labels=['注册用户', '未注册用户'], values=general_reg_user, hole=.3, marker=dict(colors=['#73C5C5', '#A2D9D9'])), row=1, col=1)
# average user distribution based on holiday/workingday.
user_fig.add_trace(go.Pie(hoverinfo='label+percent', labels=['节假日', '工作日'], values=avrg_user, hole=.3, marker=dict(colors=['#F6D173', '#F9E0A2'])), row=1, col=2)
# general seasonal user distribution.
user_fig.add_trace(go.Pie(labels=['春天', '夏天', '秋天', '冬天'], values=seasonal_counts['count'], textinfo='label+percent', marker=dict(colors=['#FEFAE0', '#FAEDCE', '#E0E5B6', '#CCD5AE']), hole=.3, hoverinfo='label+percent'), row=1, col=3)

# minor tweaks.
user_fig.update_layout(title_text="用户分布", showlegend=True)

气候对用户活跃度的影响¶

此三维散点图展示的是温度、湿度与用户数量之间的关系。图中的每个点都表示一个特定时间点的观测值,颜色深浅代表了用户数量的多少,即颜色越深,用户数量越多;反之,颜色越浅,用户数量越少。

从图中可以观察到以下几点:

  1. 温度与用户数量的关系:

    • 在低温区域(约20°C左右),随着温度升高,用户数量呈现上升的趋势。
    • 当温度超过30°C之后,用户数量开始逐渐减少。这可能是因为高温天气降低了人们的骑行意愿。
  2. 湿度与用户数量的关系:

    • 湿度过高(大于70%)时,用户数量明显降低。这说明高湿度环境下,用户对共享单车的需求减弱。
  3. 温度和湿度的交互效应:

    • 在低温和适中湿度条件下(大约20-30°C和40%-60%之间),用户数量较多。
    • 高温下如果伴随高湿度,则用户数量显著下降。
  4. 峰值区域:

    • 图表中存在一个明显的峰值区域,位于温度约为25°C、湿度约为50%附近。这表明在这种气候条件下,用户对共享单车的需求最高。

综上所述,共享单车的使用量受到气温和空气湿度的双重影响。当温度适宜并且湿度适中时,用户数量达到最大值。相反,过高的温度或湿度过大会导致用户数量减少。这些发现对于制定营销策略和优化服务时间表具有重要意义,例如在预测高峰时段时考虑气象因素,从而提高车辆分配效率和服务质量。

In [51]:
# define scatter map
scatter_fig_wea = go.Figure(data=[go.Scatter3d(x = temp, y = humidity, z = count, mode = 'markers', marker = dict(size = count/60, line = dict(width=0), color = count, colorscale = 'Viridis', opacity = 0.8), hovertemplate='<b>温度:</b> %{x}°C<br><b>湿度:</b> %{y}%<br><b>用户数量:</b> %{z}<extra></extra>')])
scatter_fig_wea.update_layout(
    scene=dict(
        xaxis=dict(
            title='温度 (°C)',
            range=[temp.min(), temp.max()],
            autorange=False
        ),
        yaxis=dict(
            title='湿度 (%)',
            range=[humidity.min(), humidity.max()],
            autorange=False
        ),
        zaxis=dict(
            title='用户数量',
            range=[count.min(), count.max()],
            autorange=False
        ),
        aspectmode='manual',
        aspectratio=dict(x=1, y=1, z=1)
    ),
    title='温度与湿度对用户数量的影响',
    showlegend = False,
    hovermode = 'closest',
    height=800
)

用户活跃度分析¶

基于月份分析的用户活跃度¶

描述:¶

这是一个垂直条形图,横坐标代表一年中的月份,纵坐标代表用户数量。每个条形的高度对应该月份的用户活跃数。

观察:¶

  • 从图中可以看出,用户活跃度随月份有所变化,但整体呈现上升趋势。
  • 特别是在6月至8月期间,用户活跃度达到最高点,这可能与暑假或其他假期有关。
  • 相比之下,1月至3月的用户活跃度较低,可能受到寒冷天气或春节假期的影响。

基于星期分析的用户活跃度¶

描述:¶

这是一个折线图,横坐标代表一周中的每一天,纵坐标代表用户数量。每种颜色的线代表一个特定的时间段内的用户活跃情况。

观察:¶

  • 从图中可以看到,周一至周四的用户活跃度相对稳定,但在周五和周六出现了明显的增长。
  • 周日的用户活跃度略有下降,但仍高于前半周的工作日。
  • 这表明用户在周末更加活跃,可能是因为他们有更多空闲时间参与各种活动。

基于季度分析的用户活跃度¶

描述:¶

这也是一个折线图,横坐标代表四个季节:春、夏、秋、冬,纵坐标代表用户数量。每种颜色的线代表一个特定的时间段内的用户活跃情况。

观察:¶

  • 夏季的用户活跃度最高,其次是春季和秋季,冬季的用户活跃度最低。
  • 这种季节性的变化可能与气候条件、节假日等因素有关。

用户类型按小时的平均数量及总数¶

描述:¶

这是一个折线图,横坐标代表一天中的小时,纵坐标代表用户数量。每种颜色的线代表一种用户类型,最后一条红线代表所有用户类型的总和。

观察:¶

  • 从图中可以看出,大多数用户类型的活跃度在白天(尤其是上午和下午)较高,而在夜间较低。
  • 红色的总和线显示了全天的用户活跃度分布,其中中午和傍晚达到了两个高峰期。
  • 这些结果表明,大部分用户倾向于在白天进行在线活动,尤其是在工作时间之外。

综上所述,这些图表为我们提供了关于用户活跃度的重要见解,帮助我们理解用户的行为模式及其潜在影响因素。通过深入分析这些数据,我们可以更好地满足用户的需求,提高用户体验和服务质量。

In [57]:
# First Graph.
user_active_month = px.bar(month_sorted, x = 'month', y = 'count', title='基于月份分析的用户活跃度', color='count')
user_active_month.update_xaxes(
    categoryorder='array',
    categoryarray=sort_order,
    ticktext=['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
    tickvals=sort_order
)
user_active_month.update_traces(
    hovertemplate='<b>月份:</b> %{x}<br>' +  
                  '<b>用户数量:</b> %{y:.2f}<br>' +  
                  '<extra></extra>'  
)

# Second Graph.
user_active_week = px.line(user_active_week_cmp, x = 'hour', y = 'count', color = 'weekday', title = '基于星期分析的用户活跃度', labels={'datetime': '时间', 'value': '数值'}, markers=True)
user_active_week.update_yaxes(categoryorder='array', categoryarray=hue_order)
legend_items = user_active_week.data

sorted_legend_items = sorted(legend_items, key=lambda item: hue_order.index(item.name))

user_active_week.data = []
for item in sorted_legend_items:
    user_active_week.add_trace(item)

user_active_week.for_each_trace(lambda t: t.update(
    name={
        'Monday': '星期一',
        'Tuesday': '星期二',
        'Wednesday': '星期三',
        'Thursday': '星期四',
        'Friday': '星期五',
        'Saturday': '星期六',
        'Sunday': '星期日'
    }.get(t.name, t.name),
    hovertemplate="<b>时间:</b> %{x}<br>" + \
                  "<b>用户数量:</b> %{y:.2f}<br>" + \
                  "<extra></extra>"
))

# Third Graph.
user_active_season = px.line(user_active_season_cmp, x = 'hour', y = 'count', color = 'season', title = '基于季节分析的用户活跃度', labels={'datetime': '时间', 'value': '数值'}, markers=True)
user_active_season.for_each_trace(lambda t: t.update(
    name={
        '1': '春',
        '2': '夏',
        '3': '秋',
        '4': '冬'
    }.get(t.name, t.name),
    hovertemplate="<b>时间:</b> %{x}<br>" + \
                  "<b>用户数量:</b> %{y:.2f}<br>" + \
                  "<extra></extra>"
))

# Fourth Graph.
user_active_time = go.Figure()

for user_type in ['casual', 'registered']:
    user_active_time.add_trace(go.Scatter(
        x=hour_aggregated[hour_aggregated['user_type'] == user_type]['hour'],
        y=hour_aggregated[hour_aggregated['user_type'] == user_type]['user_count'],
        mode='lines+markers',
        name=user_type.capitalize(),
        hovertemplate='<b>时间:</b> %{x}<br>' +
                      '<b>用户数量:</b> %{y:.2f}<br>' +
                      '<extra></extra>'
    ))
user_active_time.for_each_trace(lambda t: t.update(
    name={
        'Casual': '群众',
        'Registered': '注册用户'
    }.get(t.name, t.name),
))
user_active_time.add_trace(go.Scatter(
    x=hour_aggregated['hour'],
    y=hour_aggregated['total'],
    mode='lines+markers',
    name='总数',
    line=dict(color='red', dash='dash'),
    marker=dict(color='red', size=8),
    hovertemplate='<b>时间:</b> %{x}<br>' +
                  '<b>总数:</b> %{y:.2f}<br>' +
                  '<extra></extra>'
))

fig = make_subplots(rows=2, cols=2,
                    subplot_titles=("基于月份分析的用户活跃度", "基于星期分析的用户活跃度",
                                    "基于季节分析的用户活跃度", "用户类型按小时的平均数量及总数"))

for trace in user_active_month.data:
    fig.add_trace(trace, row=1, col=1)
for trace in user_active_week.data:
    fig.add_trace(trace, row=1, col=2)
for trace in user_active_season.data:
    fig.add_trace(trace, row=2, col=1)
for trace in user_active_time.data:
    fig.add_trace(trace, row=2, col=2)


fig.update_layout(height=800, title_text="用户活跃度分析", coloraxis_showscale=False, legend_title_text="用户类型")
fig.update_yaxes(title_text="用户数量")
fig.update_xaxes(
    title_text="月份",
    categoryorder='array',
    categoryarray=sort_order,
    ticktext=['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
    tickvals=sort_order, 
    row=1, 
    col=1
)
fig.update_xaxes(title_text="时间 (24小时制)", row=1, col=2)
fig.update_xaxes(title_text="时间 (24小时制)", row=2, col=1)
fig.update_xaxes(title_text="时间 (24小时制)", row=2, col=2)
fig.show()

异常分析¶

总览¶

  • 描述:这张图展示了整体数据的分布情况。
  • 观察点:
    • 数据主要集中在较低的值范围内,中位数大约在200左右。
    • 存在一个显著的异常值,其值远高于其他数据点,接近1000。

按季节分布¶

  • 描述:这张图显示了不同季节下数据的分布情况。
  • 观察点:
    • 第一季的数据集中度较高,中位数约为300。
    • 第二、三、四季的数据分布较为均匀,但第三季的中位数略高一些,约在350左右。
    • 各季度均存在一定的离群值,其中第一季尤为明显。

按小时分布¶

  • 描述:这张图展示了每个小时段内数据的变化趋势。
  • 观察点:
    • 在凌晨时段(00:00至06:00),数据量较少,且波动较大。
    • 上午时段(07:00至12:00)开始出现明显的增长趋势,尤其是上午8点到10点之间。
    • 下午时段(13:00至18:00)达到峰值,尤其是在下午4点到6点期间。
    • 夜间时段(19:00至23:00)数据逐渐减少,但仍保持一定水平。

按工作日分布¶

  • 描述:这张图比较了工作日(标记为1)和非工作日(标记为0)之间的数据差异。
  • 观察点:
    • 工作日和非工作日的数据分布基本一致,中位数都在200左右。
    • 非工作日的上界稍低,而工作日的上界稍高,表明工作日可能有更多的极端值。

综合分析¶

从这四张图可以看出,数据的整体分布呈现出一定的规律性。具体来说,在不同的时间维度(季节、小时、是否是工作日)下,数据的中心位置和分散程度有所不同。例如,夏季和冬季的数据分布相对更宽泛;白天特别是下午时段的数据量较多;工作日和非工作日的数据分布没有显著差别。此外,还存在一些显著的异常值,特别是在总览图和按季节分布图中,这些异常值可能是由于某些特殊事件或测量误差导致的。

In [53]:
fig_2 = make_subplots(rows=2, cols=2,
                    subplot_titles=("总览", "按季节分布", "按小时分布", "按工作日分布"))

fig_2.add_trace(go.Box(y=df['count'], name='总览'), row=1, col=1)

fig_2.add_trace(go.Box(x=df['season'], y=df['count'], name='按季节分布'), row=1, col=2)

fig_2.add_trace(go.Box(x=df['hour'], y=df['count'], name='按小时分布'), row=2, col=1)

fig_2.add_trace(go.Box(x=df['workingday'], y=df['count'], name='按工作日分布'), row=2, col=2)

fig_2.update_layout(
    height=1000,
    showlegend=False
)

fig_2.update_xaxes(title_text="季节", row=1, col=2)
fig_2.update_xaxes(title_text="小时", row=2, col=1)
fig_2.update_xaxes(title_text="工作日", row=2, col=2)
fig_2.update_yaxes(title_text="用户数量", col=1)
fig_2.update_yaxes(title_text="用户数量", col=2)

fig_2.show()

机器学习¶

数据表¶

  • RandomForest: 在训练集上表现出色,但在测试集上的表现略差。
  • GradientBoosting: 在两个数据集上的表现都较好,但不如RandomForest稳定。
  • AdaBoost: 性能一般,在两个数据集上的表现接近。
  • LinearRegression: 表现最差,尤其是在测试集上。

图表¶

(a) RandomForest - 拟合与预测¶

该图表展示了RandomForest模型在训练集和测试集上的预测结果。从图中可以看到,模型在大部分时间点上的预测值(绿线)与实际值(蓝线)非常接近,表明该模型具有良好的拟合能力。然而,在某些时间段内,预测值与实际值之间存在较大的偏差,这可能是由于模型过拟合或者数据本身存在的噪声导致的。

(b) GradientBoosting - 拟合与预测¶

此图表同样展示了GradientBoosting模型在训练集和测试集上的预测情况。可以看出,虽然整体趋势与实际情况相符,但在一些细节处仍然存在一定的差异。这种现象通常意味着模型可能存在欠拟合的问题,即其复杂度不足以捕捉到所有相关模式。

(c) AdaBoost - 拟合与预测¶

对于AdaBoost模型而言,其预测曲线显示出更明显的波动性和不稳定性。这意味着尽管该方法能够有效地提高弱分类器的整体性能,但它也可能引入额外的不确定性因素,从而降低最终输出的质量。

(d) LinearRegression - 拟合与预测¶

最后一个是线性回归模型的结果。显然,线性回归作为一种简单而强大的统计技术,在许多情况下都能提供可靠的结果。不过,正如预期那样,它无法完全捕捉非线性关系的存在,因此在某些区域内的预测准确性会受到限制。

LIME解释¶

(a) RandomForest - LIME解释¶

  • 展示了RandomForest模型中各个特征的重要性。
  • 特征包括小时、年份、温度、季节、湿度等。
  • 每个特征的贡献度用条形图表示,蓝色为正面影响,橙色为负面影响。

(b) GradientBoosting - LIME解释¶

  • 展示了GradientBoosting模型中各个特征的重要性。
  • 特征包括小时、年份、温度、季节、湿度等。
  • 每个特征的贡献度用条形图表示,蓝色为正面影响,橙色为负面影响。

(c) AdaBoost - LIME解释¶

  • 展示了AdaBoost模型中各个特征的重要性。
  • 特征包括小时、年份、温度、季节、湿度等。
  • 每个特征的贡献度用条形图表示,蓝色为正面影响,橙色为负面影响。

(d) LinearRegression - LIME解释¶

  • 展示了LinearRegression模型中各个特征的重要性。
  • 特征包括小时、年份、温度、季节、湿度等。
  • 每个特征的贡献度用条形图表示,蓝色为正面影响,橙色为负面影响。
In [207]:
%config InlineBackend.figure_format = 'retina'
lis=['a','b','c','d']
colors = {
    "真实值": "blue",
    "训练预测": "green",
    "测试预测": "red"
}
results = []
lime_htmls = []
col = [tuple(np.random.rand(3)) for _ in range(3)]
fig, axs = plt.subplots(2, 2, figsize=(17, 12), dpi=200)
for i, (name, model) in enumerate(models.items()):
    # train models.
    model.fit(X_train, Y_train)
    
    # prediction.
    y_pred_train = model.predict(X_train)
    y_pred_test = model.predict(X_test)
    
    # score.
    mse_train = mean_squared_error(Y_train, y_pred_train)
    mse_test = mean_squared_error(Y_test, y_pred_test)
    r2_train = r2_score(Y_train, y_pred_train)
    r2_test = r2_score(Y_test, y_pred_test)

    results.append({
        "模型": name,
        "训练集的均方误差": mse_train,
        "测试集的均方误差": mse_test,
        "训练集的R²": r2_train,
        "测试集的R²": r2_test
    })

    ax = axs[i // 2, i % 2]    

    # true values.
    ax.plot(np.arange(len(X)), Y, label="真实值 (测试)", color=col[0], linewidth=2,linestyle="--")
    
    # train prediction.
    ax.plot(np.arange(len(Y_train)), y_pred_train, label=f"{name} 预测 (训练)", color=col[1], linestyle="--")
    
    # test prediction.
    ax.plot(np.arange(len(Y_train), len(Y_train) + len(Y_test)), y_pred_test, label=f"{name} 预测 (测试)", color=col[2], linestyle="--")

    ax.set_title(f"({lis[i]}) {name} - 拟合与预测", fontsize=24, fontweight='semibold', fontproperties=font_prop)
    ax.set_ylabel("用户数量", fontsize=18, fontweight='semibold', fontproperties=font_prop)
    
    # tweaks.
    ax.spines['left'].set_linewidth(2.5)
    ax.spines['bottom'].set_linewidth(2.5)
    ax.spines['top'].set_visible(True)
    ax.spines['right'].set_visible(True)

    ax.tick_params(axis='x', labelsize=12, width=2)
    ax.tick_params(axis='y', labelsize=14, width=2.5)

    # legend.
    leg=ax.legend(loc='upper left',bbox_to_anchor=(0, 1), ncol=1, fontsize=12,
            edgecolor='black', facecolor='white', framealpha=0)
    for i, text in enumerate(leg.get_texts()):
        text.set_color(col[i])
        text.set_font_properties(font_prop)

    # lime.
    explainer = lime.lime_tabular.LimeTabularExplainer(
        X_train.values, feature_names=X_train.columns, class_names=['用户数量'], verbose=True, mode='regression'
    )
    idx = 10
    exp = explainer.explain_instance(X_test.iloc[idx], model.predict, num_features=5)
    
    lime_html = exp.as_html()
    lime_htmls.append(lime_html)

results_df = pd.DataFrame(results)
display(results_df.head())
plt.tight_layout()
plt.show()

for i, html_content in enumerate(lime_htmls):
    display(HTML(f'<h2>({lis[i]}) {list(models.keys())[i]} - LIME解释</h2>'))
    display(HTML(html_content))
Intercept 300.7742182546543
Prediction_local [-36.87329768]
Right: 7.87
Intercept 294.525471206987
Prediction_local [-38.21660821]
Right: 4.9147296338089435
Intercept 341.8157786240902
Prediction_local [11.56388283]
Right: 32.13664596273292
Intercept -123.78461429089657
Prediction_local [-164919.82845724]
Right: 15.386967180238571
模型 训练集的均方误差 测试集的均方误差 训练集的R² 测试集的R²
0 RandomForest 268.033922 1791.554298 0.991819 0.945722
1 GradientBoosting 4334.617192 4625.437844 0.867690 0.859865
2 AdaBoost 11583.333863 11469.074163 0.646430 0.652525
3 LinearRegression 20034.288221 19903.013706 0.388474 0.397005

(a) RandomForest - LIME解释

(b) GradientBoosting - LIME解释

(c) AdaBoost - LIME解释

(d) LinearRegression - LIME解释