爱数课实验 | 首尔共享自行车需求数据可视化分析

2022-04-01 11:28:57 浏览数 (3)

爱数课:idatacourse.cn

领域:消费

简介:近些年,“共享单车”模式迅速地在全球各大城市中流行起来,但随着资本的逐步退潮,共享单车企业需寻求新的盈利模式,首要任务便是探究共享单车使用量的影响因素。本案例使用Matplotlib包和Seaborn的可视化库,对首尔地区一共享单车公司在2017年到2018年的使用量数据集进行可视化分析,并利用线性回归等模型预测单车使用量,得出共享单车使用量影响因素分析结论。

数据:

./dataset/SeoulBikeData.csv

1. 数据简介

该数据集包含在首尔自行车共享系统中每小时出租的公共自行车的数量,以及相应的天气数据和假日信息,包含14个属性,8760条数据,下表中展示了数据集所有字段的名称及对应含义:

列名

类型

含义说明

date

字符型

日期

Rented Bike Count

整型

租用自行车计数 - 每小时租用自行车数

Hour

整型

小时 - 一天中的小时

Temperature

浮点型

温度 - 摄氏温度

Humidity(%)

整型

湿度

Wind speed (m/s)

浮点型

风速

Visibility (10m)

整型

能见度

Dew point temperature

浮点型

露点温度 - 摄氏度

Solar Radiation (MJ/m2)

浮点型

太阳辐射

Rainfall(mm)

浮点型

降雨量

Snowfall (cm)

浮点型

降雪量

Seasons

字符型

季节

Holiday

字符型

假期

Functioning Day

字符型

功能日 - NoFunc(非功能时间),Fun(功能时间)

代码语言:javascript复制
from matplotlib.font_manager import _rebuild # MAC版本matplotlib显示中文
_rebuild()

1.1 导入数据集与必要的模块

代码语言:javascript复制
import pandas as pd # 读取数据、离散化
import numpy as np

import matplotlib.pyplot as plt # 可视化
import matplotlib.dates as dates
import matplotlib.ticker as ticker
import datetime
import seaborn as sns

from matplotlib.font_manager import _rebuild # MAC版本matplotlib显示中文
# _rebuild()

from scipy import stats
from scipy.stats import norm, skew

from sklearn.preprocessing import StandardScaler  # Z-Score标准化
from sklearn.preprocessing import LabelEncoder  # 数字编码
from sklearn.preprocessing import OneHotEncoder  # One-Hot编码

首先,读取数据:

代码语言:javascript复制
data = pd.read_csv('./dataset/SeoulBikeData.csv')
data.head()
代码语言:javascript复制
# 查看数据集的基本信息
data.info()

从结果中可以看出,数据集中共包含8760条数据,没有缺失数据。

代码语言:javascript复制
# 生成描述性统计数据,统计数据集的集中趋势,分散和行列的分布情况
data.describe(include='all')

通过上表可以初步查看各列数据的大体分布等,也可以初步判断异常值情况,之后需要对可能存在异常值的特征,比如:Dew point temperatureVisibility (10m)进行判断后处理, 其余特征大致符合正常范围。

比如:Dew point temperature 是露点温度,最小值约为-30,标准差为13,则可初步判断-30有可能是一个异常值,后续需要对此进行数据处理。

代码语言:javascript复制
# 将数值型特征和非数值型特征抽取出来
num_col = list(data.columns[(data.dtypes == 'int64')|(data.dtypes == 'float64')])
str_col = list(data.columns[data.dtypes=='object'])
print('数值型特征:', len(num_col))
print('非数值型特征:', len(str_col))

由于本数据集包含10个数值型特征和4个非数值型特征,所以建模之前我们需要对非数值型特征进行处理。

1.2 数据预处理

首先,将日期一列数据类型变为datetime,方便后期对日期这一因素做分析:

代码语言:javascript复制
data['Date'] = pd.to_datetime(data['Date'])

# 设置日期为索引
data.set_index('Date',inplace=True)     # 也可使用pd.read_csv("./input/alltime_China_2020_03_27.csv",parse_dates=['date'],index_col='date')

data.index

Dew point temperature 列数据进行异常值检测:

代码语言:javascript复制
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
plt.rcParams['font.sans-serif'] = ['SimHei']  
# Matplotlib中设置字体-黑体,解决Matplotlib中文乱码问题
plt.rcParams['axes.unicode_minus'] = False    
# 解决Matplotlib坐标轴负号'-'显示为方块的问题
sns.set(font='SimHei')


## 绘制直方图
plt.figure(figsize = (12,6))                     
plt.subplot(1, 2, 1)
plt.hist(data['Dew point temperature'],
         bins=8,
         facecolor='green',
         alpha=0.5,
         edgecolor='black')
plt.title('露点温度直方图', fontsize=15)
plt.xlabel('露点温度', fontsize=15)
plt.ylabel('频率', fontsize=15)

## 绘制箱线图
plt.subplot(1, 2, 2)
plt.boxplot(data['Dew point temperature'], 
            labels=['露点温度'],
            patch_artist = True,
            boxprops = {'color':'black','facecolor':'yellow'}, 
            flierprops = {'markerfacecolor':'red','color':'black'})

plt.title('露点温度箱线图', fontsize=15)
plt.xticks(fontsize=15)

由此得知,Dew point temperature 不存在异常值,因此不需要对此做任何处理。

Visibility (10m) 列数据进行异常值检测

代码语言:javascript复制
## 绘制直方图
plt.figure(figsize = (12,6))                     
plt.subplot(1, 2, 1)
plt.hist(data['Visibility (10m)'],
         bins=8,
         facecolor='green',
         alpha=0.5,
         edgecolor='black')
plt.title('能见度直方图', fontsize=15)
plt.xlabel('能见度(10m)', fontsize=15)
plt.ylabel('频率', fontsize=15)

## 绘制箱线图
plt.subplot(1, 2, 2)
plt.boxplot(data['Visibility (10m)'], 
            labels=['能见度(10m)'],
            patch_artist = True,
            boxprops = {'color':'black','facecolor':'yellow'}, 
            flierprops = {'markerfacecolor':'red','color':'black'})

plt.title('能见度箱线图', fontsize=15)
plt.xticks(fontsize=15)

由此得知,Visibility (10m) 不存在异常值,因此不需要对此做任何处理。

2. 使用量影响因素可视化分析

2.1 利用直方图查看单车使用量以及温度,湿度和风速的分布情况

代码语言:javascript复制
col1 = data.columns[[0,2,3,4]].tolist()
fig,ax = plt.subplots(2,2,figsize = (8,6))

for i in range(0,len(col1)):
    plt.subplot('22' str(i 1))
    x = data[col1[i]]
    sns.distplot(x,color = 'lightpink') 
plt.tight_layout()
plt.show()

由图可知,Rented Bike Count 集中分布在0-500之间;Temperature呈正态分布,2017-2018年天气正常,集中在15-30度;Humidity(%)大致呈正态分布,集中在40-60%;2017-2018年首尔的Wind speed大部分在0-2m/s,但偶尔会出现风速较大的天气。

2.2 利用热力图探究各个特征之间的相关性

代码语言:javascript复制
def correlation_heatmap(df):
    _ , ax = plt.subplots(figsize =(12, 10))
    colormap = sns.diverging_palette(220, 10, as_cmap = True)
    
    _ = sns.heatmap(
        df.corr(), 
        cmap = 'Reds',
        square=True, 
        cbar_kws={'shrink':.9 }, 
        ax=ax,
        annot=True, 
        linewidths=0.1,vmax=1.0, linecolor='white',
        annot_kws={'fontsize':12 }
    )
    
    plt.title('共享单车特征相关性图', y=1.05, size=15)

correlation_heatmap(data)

由图可知,首先Dew point temperatureTemperature 存在较高的相关性,符合常识露点温度和温度成正相关性,但是其余大部分数据特征两两之间的相关性较弱。

其次,Rented Bike Count 与大多特征之间存在较弱的相关性,和Temperature存在0.54的相关性,因此,可以进一步分析两个特征之间的关系。

2.3 利用散点图进一步探究单车使用量与温度、湿度和风速的关系

我们使用散点图并拟合回归线来观察Rented Bike CountTemperature的关系:

代码语言:javascript复制
sns.regplot(x=data['Temperature'],y=data['Rented Bike Count'],data=data, line_kws={"color":"r"}, scatter_kws={"s": 10})     

plt.xlabel('温度', fontsize=8)
plt.ylabel('自行车租借数', fontsize=8)
plt.title("自行车租借数与温度散点图")
plt.show()

Rented Bike CountHumidity(%)的关系:

代码语言:javascript复制
sns.regplot(x=data['Humidity(%)'],y=data['Rented Bike Count'],data=data, line_kws={"color":"r"},scatter_kws={"s": 10},color='y')     

plt.xlabel('湿度', fontsize=8)
plt.ylabel('自行车租借数', fontsize=8)
plt.title("自行车租借数与湿度散点图")
plt.show()

单车使用量与风速的关系:

代码语言:javascript复制
sns.regplot(x=data['Wind speed (m/s)'],y=data['Rented Bike Count'],data=data, line_kws={"color":"r"},scatter_kws={"s": 10},color="g")     

plt.xlabel('风速', fontsize=8)
plt.ylabel('自行车租借数', fontsize=8)
plt.title("自行车租借数与风速散点图")
plt.show()

从图片可以看出,温度和风速与使用量呈正相关关系,随着温度的升高和风速的增加租车量在增加;湿度与使用量呈负相关关系,随着湿度的升高租车量在下降。

2.4 利用条形图探究季节和假期的分布情况

对于离散型特征,我们可以使用条形图绘制其每一种取值的样本数量。可以通过Seaborn的 countplot 函数来表示特征情况

代码语言:javascript复制
df=pd.DataFrame()
df['季节']=data['Seasons']
df['假期']=data['Holiday']

fig, ax =plt.subplots(1,2,figsize=(12,4))

sns.countplot(df['季节'], ax=ax[0])
sns.countplot(df['假期'], ax=ax[1])
fig.show()

由图可以得知共享单车的使用总量几乎不受季节的影响, 非节假日的时候共享单车的使用量明显高于节假日,可初步判断共享单车的适用人群多为工作者。

2.5 利用箱线图探究单车使用量与季节和假期的关系

代码语言:javascript复制
fig, ax =plt.subplots(1,2,figsize=(12,4))
sns.boxplot(x='Seasons',y='Rented Bike Count',data=data, ax=ax[0])
sns.boxplot(x='Holiday',y='Rented Bike Count',data=data, ax=ax[1])

plt.title('Miles per Gallon by Vehicle Origin')
plt.xlabel('Country of Origin')
plt.ylabel('Miles per Gallon (MPG)')

fig.show()

由图可知,冬天的时候共享单车使用量大多在250左右,而春、夏、秋的使用量多趋于1000左右。无假期的时候的使用量趋于500左右,而假期的使用量趋于250左右。

3. 利用回归模型预测单车的使用量

代码语言:javascript复制
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression, Lasso, Ridge
from sklearn.metrics import r2_score
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder 

3.1 特征编码

首先对Functioning Day,Seasons,Holiday列进行编码

代码语言:javascript复制
data = pd.get_dummies(data,columns=['Seasons'])
data["Functioning Day"] = pd.factorize(data["Functioning Day"])[0].astype(np.uint16)
data["Holiday"] = pd.factorize(data["Holiday"])[0].astype(np.uint16)

data

3.2 对于特征进行对数转换

Rented Bike Count(共享单车租借数量)为我们目标特征,需要对它的分布进行查看。(Q-Q图用于检验数据是否呈现正态分布,如果图中的点大都落在y=x线上,则符合正态分布)

代码语言:javascript复制
fig, ax = plt.subplots(1,2,figsize=(10,4))
sns.distplot(data['Rented Bike Count'], hist=True, kde=True, fit=norm, ax=ax[0])
ax[0].set_ylabel('Frequency')
ax[0].set_xlabel('Rented Bike Count')
ax[0].set_title('Distribution of Rented Bike Count')
stats.probplot(data['Rented Bike Count'], dist="norm", plot=plt)
plt.show()

上述两个图表明,Rented Bike Count的分布不符合正态分布,呈右偏分布。如果后续分析需要建立线性模型,需要对Rented Bike Count进行对数转换,使其呈现正态分布。

代码语言:javascript复制
# 对数转换
data['Rented Bike Count'] = np.log1p(data['Rented Bike Count'])

# 查看转换后数据
data['Rented Bike Count']
代码语言:javascript复制
# 再次检验
fig, ax = plt.subplots(1,2,figsize=(10,4))
sns.distplot(data['Rented Bike Count'], hist=True, kde=True, fit=norm, ax=ax[0])
ax[0].set_ylabel('Frequency')
ax[0].set_xlabel('Rented Bike Count')
ax[0].set_title('Distribution of Rented Bike Count')
stats.probplot(data['Rented Bike Count'], dist="norm", plot=plt)
plt.show()

对风速分布进行查看:

代码语言:javascript复制
fig, ax = plt.subplots(1,2,figsize=(10,4))
sns.distplot(data['Wind speed (m/s)'], hist=True, kde=True, fit=norm, ax=ax[0])
ax[0].set_ylabel('Frequency')
ax[0].set_xlabel('Wind speed (m/s)')
ax[0].set_title('Distribution of Wind speed')
stats.probplot(data['Wind speed (m/s)'], dist="norm", plot=plt)
plt.show()

上述两个图表明,Wind speed (m/s)的分布不符合正态分布,呈右偏分布。如果后续分析需要建立线性模型,需要对Wind speed (m/s)进行对数转换,使其呈现正态分布。

代码语言:javascript复制
# 对数转换
data['Wind speed (m/s)'] = np.log1p(data['Wind speed (m/s)'])

# 再次检验
fig, ax = plt.subplots(1,2,figsize=(10,4))
sns.distplot(data['Wind speed (m/s)'], hist=True, kde=True, fit=norm, ax=ax[0])
ax[0].set_ylabel('Frequency')
ax[0].set_xlabel('Wind speed (m/s)')
ax[0].set_title('Distribution of Wind speed (m/s)')
stats.probplot(data['Wind speed (m/s)'], dist="norm", plot=plt)
plt.show()

3.3 连续性数据标准化

下面我们将使用sklearn中的StandardScaler方法,对数据集data中的friends列做Z-Score标准化,使得处理后的数据具有固定均值和标准差。

代码语言:javascript复制
data.loc[:,data.columns[3:11]] = data[data.columns[3:11]].apply(lambda x:  (x-x.mean())/x.std())
代码语言:javascript复制
# 查看处理后的数据形式
data

3.4 划分训练集-测试集

比例为70%(训练)--30%(测试), 在Sklearn中我们可以使用model_selection模块的train_test_split方法进行训练集和测试集划分。

代码语言:javascript复制
y = data['Rented Bike Count']
X = data.copy().drop(['Rented Bike Count'], axis=1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)

3.5 线性回归模型

代码语言:javascript复制
lr = LinearRegression()
lr.fit(X_train, y_train)

# 预测
y_pred0 = lr.predict(X_test)
print("线性回归的预测得分为:", lr.score(X_test, y_test))

#显示特征的回归系数
print(lr.intercept_)   #截距,线性函数中的常数项
print(lr.coef_)   #线性系数,从线性系数中可以看出各列对预测结果影响的程度

# 模型评价
print('r2_score: %.2f'
      % r2_score(y_test, y_pred0))
代码语言:javascript复制
# 残差图(残差标准化)
plt.scatter(y_pred0, y_pred0 - y_test, c="g", alpha=0.6, s=9)
plt.hlines(y=0, xmin=-2.5, xmax=8, color="red",alpha=0.6)
plt.ylabel("Residuals")
plt.xlabel("Predict")

3.6 Ridge 模型

代码语言:javascript复制
# alpha系数越大,惩罚越大
rr = Ridge(alpha=0.01) 
rr.fit(X_train, y_train)

# 使用较大的alpha值
rr100 = Ridge(alpha=100) 
rr100.fit(X_train, y_train)

# 预测
y_pred1 = rr.predict(X_test)
y_pred2 = rr100.predict(X_test)

Ridge_train_score = rr.score(X_train,y_train)
Ridge_test_score = rr.score(X_test, y_test)

Ridge_train_score100 = rr100.score(X_train,y_train)
Ridge_test_score100 = rr100.score(X_test, y_test)

print ("ridge regression train score low alpha:", Ridge_train_score)
print ("ridge regression test score low alpha:", Ridge_test_score)
print ("ridge regression train score high alpha:", Ridge_train_score100)
print ("ridge regression test score high alpha:", Ridge_test_score100)
代码语言:javascript复制
# 残差图
plt.scatter(y_pred1, y_pred1 - y_test, c="y", alpha=0.6, s=9)
plt.scatter(y_pred2, y_pred2 - y_test, c="b", alpha=0.6, s=9)
plt.hlines(y=0, xmin=-2, xmax=8, color="red",alpha=0.6)
plt.ylabel("Residuals")
plt.xlabel("Predict")

3.7 Lasso模型

代码语言:javascript复制
#Lasso回归(L2损失   L1正则)
from sklearn.linear_model import LassoCV
 
alphas = [0.01 , 0.1 , 1 , 10 ,20, 30 ,40 , 100]

lasso = LassoCV(alphas = alphas)
lasso.fit(X_train, y_train)

#输出最优参数
print(lasso.alpha_)

#输出线性系数,得到最优参数后,可以进一步选择该最优参数附近值再做一轮以得到更好的参数
print(lasso.coef_)
# 使用LinearRegression模型自带的评估模块(r2_score),并输出评估结果
print('LassoCV_r2_score:', lasso.score(X_test, y_test))

4. 总结

综上所述,我们得出结论:

  1. 共享单车在夏季和秋季使用量最大
  2. 随着温度的升高和风速的增加租车量在增加,随着湿度的升高租车量在下降
  3. Ridge 模型和线性回归模型的效果最好

爱数课(iDataCourse)是一个面向院校的大数据和人工智能课程和资源平台。平台提供权威的课程资源、数据资源、案例实验资源,助力院校大数据和人工智能专业建设,课程建设和师资能力建设。

0 人点赞