Python大数据分析4:案例-安装第三方库出问题怎么办?

这个内容属于选学内容,大家可以根据需求酌情学习。不少同学都反映,在后期课程的学习中,使用到的pandas和tushare等第三方库无法顺利安装,这里我们简单说明下。

首先对于任何第三方库,我们都可以在PyCharm项目中安装。比如我们使用pandas这个库来学习数据查询分析,大家可以在数据查询分析章节的第一节课中详细了解标准安装方法,这里简单先说明下。

在当前项目中选择文件菜单——设置,在打开的界面中,选择当前项目下的项目解释器,并点击右上角的加号,此时可以在新打开的界面上方的输入框中输入,比如pandas,此时应该能够看到左边访问网络,加载显示基本信息的内容,如果可以正常显示这样的详细信息,那么建议大家选择下版本类型。在右下方,选择指定版本,建议选择1.0.1,因为课程介绍使用该版本,主要为了避免因为版本差异可能带来的不同,也方便大家调试和交流。此时即可点击安装按钮安装。

安装好应该没有任何错误提示,底部显示绿色的成功提示,并且这个按钮将变为灰色。此时你会发现安装的不仅有pandas,还有其他的一些必需的配套库,这些都是自动安装,非常方便。

但是也有可能出现错误,原因很多。简单的处理方法可以尝试换个时间、换个网络。也建议大家使用最新的Windows系统,比如我们这里测试的环境都是Win10,安装过程一般都没有问题。但是如果确实存在问题,我们该怎么办呢?

可以尝试在PyCharm终端直接安装,这是第二种方法。选中PyCharm下方的Terminal(终端),输入:

pip install pandas

你会发现同样的安装过程,而且还能提示下载的库文件名称、进度等等信息。

我们也可以通过指定版本号来安装,方法是加上双等于号和版本号:

pip install pandas==1.0.1

不过,这样安装可能与前面的方法区别不大,因为在很多情况下,可能是因为这些第三方库所在的网站访问不稳定导致无法安装。所以利用这种方法,我们可以自由选择所需的文件镜像源:

pip install -i  https://mirrors.aliyun.com/pypi/simple/ pandas

请注意这里-i参数后两个,分别是镜像文件网站,和准备安装的pandas库。你会发现速度非常快,而且很稳定。这里我们使用的是国内阿里云的镜像文件。关于镜像网站,这是一些常见的镜像文件站点:

加州大学:https://www.lfd.uci.edu/~gohlke/pythonlibs

清华:https://pypi.tuna.tsinghua.edu.cn/simple

阿里云:http://mirrors.aliyun.com/pypi/simple

中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple

豆瓣:http://pypi.douban.com/simple

第三种,可以利用独立的whl文件来安装。Whl文件就是第三方库的原始文件,前面的方法也就是通过网络去下载这些文件自动安装,如果网络存在问题,那么我们可以利用专门得到的whl文件直接安装。

比如你有一个别人给你的pandas安装文件,你可以直接粘贴到你的项目中。你选中文件复制,并粘贴到PyCharm的项目中。此时会有一个提示,直接点击OK即可。你将能看到这个文件。还是在终端窗格中输入:

pip install pandas-1.0.1-cp37-cp37m-win_amd64.whl

后面就是完整的文件名称。

很快你也能看到安装的过程。

当然,你可能会好奇如何得到这个安装文件?其实,在刚才介绍的镜像文件网站中,这些都可以找到,比如这是阿里云的镜像,文件很多,加载要等一会,我们还是以pandas为例。全部加载完毕后,可以拖拽寻找下pandas,再次点击,即可看到各种版本的文件。比如按下Ctrl+F查找1.0.1,但是哪一个才是我们所需要的呢?这里有两个关键信息:一是个Python的版本,一个是自己操作系统的版本。大家应该记得我们使用的是3.7版本,因此应该选择cp3.7那几个文件。当前我使用的是Windows操作系统,而且是64位的Win10。这是目前最为常见的配置。于是就可以选择win_amd64,到此文件就已经只有一个了。大家可以根据自己的情况做出合理的选择。当然,我们也可以从一些其他可靠的来源单独获得此类whl文件。

第四种方法可以使用复制全局环境。所谓全局环境,是指电脑系统已经安装好了一些第三方库,只是PyCharm项目还没有安装。因此我们必须将系统安装好的第三方库应用于这些PyCharm项目中。

我们还是在项目中,选择设置,找到项目解释器,点击右上角的那个齿轮按钮,选择添加,在新建环境中,增加了新的名称,比如在现有的名称后修改下。同时选中下面的继承全局站点包,点击OK。即可看到全部系统已有的包都加进来了。

可能大家会问,系统这些全局包是怎么加的?比较方便的方法是安装Anaconda,它会自动安装所有常见的相关第三方包,这些本课程不再深入讲解。

第五种方法可以说是最为原始,但是一定是最有效的方法。那就是直接将已经安装好的电脑上项目的库直接复制过来。

比如这是别人给我们的项目中库文件,我们全部选中复制,在当前PyCharm项目中,右击项目选择在文件夹显示,可以看到当前的项目,双击打开,再双击venv,双击Lib,双击site-packages,将刚才那些库文件直接粘贴过来即可。此时打开项目的项目解释器,你就发现已经全部加载成功了!

当然,大家可以很好奇这些库文件哪里来的?其实就是其他项目中site-packages下的文件,因此,你只需在可以使用的其他电脑或者项目中找到site-packages文件夹,将其中的文件全部复制,就可以粘贴到所需的其他项目中。

退一步说,如果在自己的电脑上,已有项目已经加载过所需的库,那么对于其他新建的项目,也可以直接使用。比如再次打开项目解释器,点击右上角的齿轮按钮,选中显示全部。根据需要,选择已经安装好第三方库的其他项目,即可直接将它们的库文件全部导入进来。

很难说,这里哪个方法更好用,大家可以根据自己的问题,有选择的使用,其实这也是深入了解Python第三方库使用方法的有效途径。遇到这些问题,大家不妨好好的试一试!

检索案例:在商业计划书编写中的应用

现在,商业计划书不仅已经成为各类社会创业者创业过程的第一步,也成为学生创新创业学习的重要实践环节。

首先大家需要明确的一个重要观点,就是不要觉得对于创新创业行为而言,商业计划书的编写是一件可有可无的事情。大家有没有这几种常见的情绪:

觉得写东西很烦,为什么一定要写呢?编纂良好的商业计划书,不仅有助于投资者更好的理解你的创新创业项目的内容,其实更有助于自我更好的反思和总结现有项目的特色和需要完善的地方。通常如果你觉得写不起来,或者几句话似乎就写完了,在很多情况下,这都只能反映你对项目问题了解不足,或者这个项目的价值和意义很有限。

其次,写也是一种重要的总结和思考问题的方法。很多内容只有在你静下心来写的时候,你才能真正去理解和反思其中真正的问题,不断的写作调整其实就是自我认知思路不断完善的过程。

最后,写更是一种技巧,也需要方法。

最为简单的传统起承转合依然沿用在诸如科研论文、报告论文和商业计划书等标准文体中。按照这种思路,我们来看看商业计划书的结构。主要内容包括项目策划、需求分析、项目设计(产品与服务)、竞争分析、市场营销、财务分析等。

那么文献检索在这里能够发挥什么作用呢?我们从几个方面来看下:

在项目选题方面,文献检索的价值作用最大。特色和创新性是创新创业项目最为根本的价值所在。很多人在提出此类项目想法的时候,往往仍然采取其实不是很科学的主观方法,即使是从自己的眼光能做出有效的判断,那么有益的参考也能帮助你更为深入的思考。

我们举个例子。比如我们准备从农村土地流转这个角度来提出一个创新创业项目,最为直观的思考结果是什么呢?很多学生会说,做一个APP,打通农村土地交易双方,这很好吗?其实,这往往反映了缺乏深入的思考。

比如,你能确定这个选题具有时效性吗?通常更能切合时代需要的选题往往更有价值。在百度中,输入“农村土地流转”,限定在政府站点,并且限定了最近一年的时间范围,可以看到相关政策是极受关注的话题。

再如你能确定这个选题具有新颖性吗?不妨再次就拿“农村土地流转 APP”来检索下,你很快能看到很多已有的类似项目,如“土流网”、“聚土宝”等。在这种情况下,如果你只是简单的也做一个类似的项目,很难取得更多人的认可。

当然,也有学生会说,我的APP不一样,会有自己的特色,比如增加了法律援助。这些显然不能随意确定,最好的方式还是仔细调研下已有的这些APP,看看他们都有哪些功能,也只有这样,你才能最终确定你的创意是否是真正的创意。

但是,仅仅凭借互联网上的信息来获取对相关问题的认识仍然存在着思考深度有限和创意思路不广的诸多问题。为什么我们不能使用拥有更多更好资源的文献数据库呢?

比如我们再次以“农村土地流转”为主题在百度学术中检索相关文献信息,时间限定为2015年以来,期刊类型选择CSSCI索引,很快我们看到不少高质量的文献。

比如有一篇谈到了农民工市民化与农村土地流转的关系,从人口城镇化土地城镇化的政策实施提出建议,还有提出社会保障措施与农村土地流转的相关性,这些都构成了我们思考出真正解决问题的关键内容。比如我们是否可以通过将保障服务和农村土地流转服务结合起来,形成一种更有效的服务模式?当然,这里课程只是演示思考问题的方法,并没有对该问题本身做深入分析,大家不妨自己结合感兴趣的话题来试一试。

事实上,既然现在已有很多类似的APP的交易平台,也有很多辅助政策,那么为什么还不能完全解决问题?难道仅仅是因为APP设计不好吗?想到这个地方,大家就能明白利用检索来获取对问题的了解,尤其是学术文献数据库的资源,是一个非常有价值的宝库,大家要学会使用它们。

当然商业计划书的写作并不仅仅是项目的选题,还有很多,再如用户需求。通过检索来了解用户需求是一种非常简单但很有效的方法。

比如百度搜索引擎就有一个专门的大数据营销工具,百度司南,很方便据此了解相关用户真实需求,它就是利用搜索引擎用户检索的特征来推测整体用户需求特征。其中可以进行诸如人群属性定位和分析,以一种清晰量化的方式给予用户更多观察的角度。再如百度指数等工具还能直接对相关用户需求直接进行对比分析,这里直观的比较了两种商品的关注程度。通过需求图谱扩展,用户还可以进一步了解相关商品和用户需求信息,从而扩展思路提供基础。也可以结合地域特征做出更适合自己所在地区的有针对性的分析。对于一些软件应用类产品,除了标准的搜索引擎检索外,还建议大家广泛采用应用市场对比分析方法,更好的了解相关软件应用类产品的市场需求。

当然,借助于互联网,我们还可以非常方便的获取一些商业计划书,百度文库是个常见的选择。但是直接搜索“商业计划书”可能并不是一个更好的选择,大部分高质量的都是付费文档。

可以尝试使用诸如必应等其他搜索引擎,并以文档格式限定检索来得到所需的文档。建议使用pdf文档,此处获取的几篇都是质量不错的参考文档。当然这种以整篇文档做出检索对象的方法不论是效果还是可获得性都不是很理想,最为现实的选择应该根据内容要求,有针对性的对内容分别检索。比如在土地推广的市场营销部分,我们专门检索相关文档,这种专门性的检索往往内容更为准确,有效性更强。

Python大数据分析5:回归分析(每日订单预测)

回归分析主要用于预测数值,通常可以根据一些特征属性来通过拟合函数来预测目标特征。可以分为线性回归分析和多项式回归分析等多种形式,由于线性回归通常只能把输入数据拟合成直线,因此在绝大多数应用场景中并不多见,而非线性回归分析通过函数更为复杂,相应构建的模型变得更灵活,有时结果因此而更为准确。

我们这次使用一个每日需求预测订单数据集,具体下载地址仍然是加州大学尔湾分校的数据集,可以直接点击“Data Folder”获取文件。

这个文件不同列采用分号分隔,普通的Excel无法正常区分每一列。

我们自己使用pandas读取:

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\Daily_Demand_Forecasting_Orders.csv’, sep=’;’)

pd.set_option(‘display.max_columns’, None)

print(frame.head(1))

这里使用sep参数指定了分隔符。虽然可以显示,但是部分列名过长,使用并不方便。

因此,我们可以修改部分列名。

import pandas as pd

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
print(frame.head(1))

其中,有12个特征数据,都是订单相关属性,最后一个Target表示订单总量。我们的任务就是通过前面12个特征较为准确的预测这个订单总量。这是个典型的回归分析任务。

我们先来看看线性回归,而且只考虑一个特征输入,可以称之为一元线性回归。这里选择了Non-urgent order这个特征(非紧急订单)。

import pandas as pd

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
X = frame['Non-urgent order'].values.reshape(-1, 1)
print(X)

不过,按照回归分析要求,它需要将特征数据做成二维结构,因此我们需要使用reshape来转换下。其中,第二个1表示1列,第一个-1表示行数根据实际情况,因此其实就是一维列表数据竖起来,做成了多行一列数据。

据此就可以预测了:

import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
X = frame['Non-urgent order'].values.reshape(-1, 1)
y = frame['Target']
regressor = LinearRegression()
scores = cross_val_score(regressor, X, y, scoring='r2')
print(np.mean(scores))

使用起来很简单,和前面介绍的各种机器学习一样,就是更换了模型,采用了LinearRegression,注意相应的交叉验证指标不能使用准确度,因为对于这种数值型预测,绝对的数值相等预测并不现实,常见的R方指标更为常见,它是指确定性相关系数,用于衡量模型对未知样本预测的效果,最好的得分是1.0。

虽然交叉验证很简单,但是却无法了解模型的很多细节,因此我们再次换成原始写法:

import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
import sklearn.metrics as sm

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
X = frame['Non-urgent order'].values.reshape(-1, 1)
y = frame['Target']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)
regressor = LinearRegression()
regressor.fit(X_train, y_train)
y_test_pred = regressor.predict(X_test)
print(round(sm.mean_squared_error(y_test, y_test_pred), 2))
print(round(sm.r2_score(y_test, y_test_pred), 2))
print(regressor.coef_)
print(regressor.intercept_)

这里我们自己拆分了数据集合,首先了模型训练,并进行了预测,最后还可以直接输出所需的指标。其中coef_和intercept_能分别给出此时的拟合直线的截距和系数。

为了更直观的了解,我们不妨绘制下:

import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
X = frame['Non-urgent order'].values.reshape(-1, 1)
y = frame['Target']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)
regressor = LinearRegression()
regressor.fit(X_train, y_train)
y_test_pred = regressor.predict(X_test)
plt.figure()
plt.scatter(X_test, y_test, color='green')
plt.plot(X_test, y_test_pred, color='black', linewidth=4)
plt.title('Test data')
plt.show()

这里我们使用原始数据作为散点图,因为预测数据采用的是一元线性,所以是直线结构。

从中可以清楚的看到拟合直线的特点,截距和系数完整的定义了直线的位置。

当然,一元回归效果通常不好,毕竟直线拟合太理想化。

因此,对于更多的情况,我们可以考虑使用不同的回归模型。比如如果存在着较大的异常值,这种异常值通常会对回归分析带来很明显的不利影响,我们可以考虑使用岭回归器:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Ridge
import sklearn.metrics as sm

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
X = frame['Non-urgent order'].values.reshape(-1, 1)
y = frame['Target']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)
regressor = Ridge()
regressor.fit(X_train, y_train)
y_test_pred = regressor.predict(X_test)
print(round(sm.r2_score(y_test, y_test_pred), 2))

只需直接替换线性回归模型就可以了。

当然,更有效的方法应该是考虑更多的特征,而不是只有一个特征,可以称为多元回归。我们还是使用一般的线性回归:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
import sklearn.metrics as sm

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)

y = frame['Target']
X = frame.drop(columns='Target')

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)
regressor = LinearRegression()
regressor.fit(X_train, y_train)
y_test_pred = regressor.predict(X_test)
print(round(sm.r2_score(y_test, y_test_pred), 2))

这里关键的代码只有中间两行,即将Target作为预测数据列,其他所有的列作为特征列。请注意,由于此时X已经是二维结构,因此就无需再转换。

此时的预测R方指标居然为1,这当然说明完全线性的关系,但是更多的原因在于数据量相对较小,拟合相对较为简单。

我们甚至还可以使用更为复杂的回归模型,比如多项式回归,不像线性回归只能把输入数据拟合成直线,而多项式回归模型通过拟合多项式方程来克服这类问题,从而提高模型的准确性。一般模型的曲率是由多项式的次数决定。随着模型曲率的增加,模型变得更准确。但是,增加曲率的同时也增加了模型的复杂性,因此拟合速度会变慢。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
import sklearn.metrics as sm
from sklearn.preprocessing import PolynomialFeatures

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
y = frame['Target']
X = frame.drop(columns='Target')
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)

polynomial = PolynomialFeatures(interaction_only=True)
X_train_transformed = polynomial.fit_transform(X_train)
regressor = LinearRegression()
regressor.fit(X_train_transformed, y_train)
X_test_transformed = polynomial.fit_transform(X_test)
y_test_pred = regressor.predict(X_test_transformed)

print(round(sm.r2_score(y_test, y_test_pred), 2))

这里的关键代码在于在进行任何训练预测前,都需要将原始特征数据进行拟合转变,具体是通过PolynomialFeatures的fit_transform方法进行转换,这里有两次,一次是X_train,一次是X_test,然后再将转换后的数据进行训练和预测。

和一般的一元线性回归相比,效果获得了2个百分点的提升。

再如可以使用决策树回归方法:

import pandas as pd
from sklearn.model_selection import train_test_split
import sklearn.metrics as sm
from sklearn.tree import DecisionTreeRegressor

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
y = frame['Target']
X = frame.drop(columns='Target')
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)

regressor = DecisionTreeRegressor(max_depth=6)
regressor.fit(X_train, y_train)
y_test_pred = regressor.predict(X_test)
print(round(sm.r2_score(y_test, y_test_pred), 2))

这里的max_depth可以指定决策树的深度。大家可以注意到,不同的方法、不同的参数对于不同的方法往往效果都不一样,这些就构成了大家需要继续学习的基础,我们也该从经验、理论等多方面来了解对于什么样的数据我们应该使用什么样的模型和参数设置。

在决策树回归分析的基础上,我们还可以使用更为强大的AdaBoost回归方法。

import pandas as pd
from sklearn.model_selection import train_test_split
import sklearn.metrics as sm
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import AdaBoostRegressor

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
y = frame['Target']
X = frame.drop(columns='Target')
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)

regressor = AdaBoostRegressor(DecisionTreeRegressor(max_depth=6))
regressor.fit(X_train, y_train)
y_test_pred = regressor.predict(X_test)
print(round(sm.r2_score(y_test, y_test_pred), 2))

其中,AdaBoostRegressor需要决策树回归为基础,效果也更好。

AdaBoost回归器还可以返回哪个特征最为相关:

import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import AdaBoostRegressor

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
y = frame['Target']
X = frame.drop(columns='Target')
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)
regressor = AdaBoostRegressor(DecisionTreeRegressor(max_depth=6))
regressor.fit(X_train, y_train)
y_test_pred = regressor.predict(X_test)

print(regressor.feature_importances_)

这里12个数值正好对应12个特征,其中最大的0.42对于第三个特征,即Non-urgent order。

最后我们通过一个有趣的绘图结束这个讲解。

这是根据刚才计算的特征重要性绘制的柱状图。数据我们都有了,如何绘制呢?这个需要些技巧。

先看纵轴,需要将最大的数值设定为100,于是:

feature_importances = 100.0 * (regressor.feature_importances_ / max(regressor.feature_importances_))

print(feature_importances)

再看看横轴,这是个排序的结果:

我们调用numpy的sort:

print(np.sort(feature_importances))

不过,它默认是升序。

我们来个降序:

print(np.flipud(np.sort(feature_importances)))

其中的flipud为翻转,正好为降序。

于是我们绘制:

values = np.flipud(np.sort(feature_importances))

plt.figure()

plt.bar(np.arange(12), values)

plt.show()

柱状图的横轴为0到11的12个整数,高度为刚才的特征重要度。

大家可能注意到了,这些方柱好像高低有点变化,这是因为每次计算都会因为随机选择而产生一定的变化。如果想固定下来,可以在AdaBoost回归器中设定固定的随机状态:

regressor = AdaBoostRegressor(DecisionTreeRegressor(max_depth=6), random_state=7)

此时的图表横轴还是不清楚,究竟是什么数据列对应哪一个方柱?

这里是完整的写法:

import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import AdaBoostRegressor

frame = pd.read_csv('C:\\temp\\Daily_Demand_Forecasting_Orders.csv', sep=';')
pd.set_option('display.max_columns', None)
frame.rename(columns={'Week of the month (first week, second, third, fourth or fifth week': 'week',
                      'Day of the week (Monday to Friday)': 'day',
                      'Orders from the traffic controller sector': 'sector',
                      'Target (Total orders)': 'Target'}, inplace=True)
y = frame['Target']
X = frame.drop(columns='Target')
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)

regressor = AdaBoostRegressor(DecisionTreeRegressor(max_depth=6), random_state=7)
regressor.fit(X_train, y_train)
y_test_pred = regressor.predict(X_test)

feature_importances = 100.0 * (regressor.feature_importances_ / max(regressor.feature_importances_))
values = np.flipud(np.sort(feature_importances))
index_sorted = np.flipud(np.argsort(feature_importances))
plt.figure()
plt.bar(np.arange(12), values)
plt.xticks(np.arange(12), X.columns.values[index_sorted])
plt.show()

其中增加了两行:

第一行中,argsort也是排序,但是它只给出排序好后的列表元素下标。经过翻转以后,我们再拿这个下标去设置横轴的内容,内容就是12个数据列,只不过这是通过下标来挨个获取,而这个下标的次序正好是目前按照重要性排序好的数据列的次序。

最终我们完成了绘制。大家不妨试一试。

Python大数据分析4:特征数据的处理(会员卡预测)

在上次课上,我们利用决策树进行了会员卡类型的预测。其中,我们发现不同的特征对于预测的准确度有着很大的影响。那么我们该如何选择特征呢?简单的凭借自己的认知和运气显然并不是通用的好方法。今天我们来了解一下专业的方法。

首先,我们在做数据分析前,应该对每个需要处理的数据列进行必要的了解。

常见的方法为:

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\customer.csv’)

print(frame[‘yearly_income’].describe())

对于文本数据,从结果可以看出非空的数据总数(count)、数据的种类数(unique,即相同的数据算一种)、出现频次最高的数据种类(top)及其频次(freq)。

如果需要详细了解具体取值种类,可以使用unique方法:

print(frame[‘yearly_income’].unique())

而对于数值型数据,内容更为丰富。内容主要包括计数、平均值、标准差、最小值、百分位数、最大值。一般通过这些,可以大概看出数据分布是否均匀。

print(frame[‘total_children’].describe())

不过,有些数据虽然看起来是数值,但其实不能作为一般的数值来分析。比如顾客地区ID等,显然平均值等概念并无实际意义。

print(frame[‘customer_region_id’].describe())

对于此类数值,应该处理方式和文本数据一样,使用独热编码来进行转换,可以有效避免算法故作聪明的利用数据之间的大小关系来做出反而有害的计算。

其次,数据预处理。

使用原始数据并不意味着一定要原封不动的使用他们,有时进行必要的转换和创建新的数据反而更为有效,既可以消除原始数据存在问题的产生的不利影响,也可以扩展内容特征。

简单的预处理只需对数据本身做一些数值上的技术处理。

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np
from sklearn import preprocessing

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
frame['yearly_income_new'] = preprocessing.scale(frame['yearly_income'])
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income_new", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
clf = RandomForestClassifier()
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

这里,没有直接将年收入用于计算,而是首先调用preprocessing类型的scale方法,它可以进行均值移除,也就是说将各个特征的平均值调整为0,标准差调整为1。再利用这个新的年收入计算,可以发现效果略有提升。

数据预处理还可以进行了更为深入一些。

比如我们来看看年收入这个数据列。为方便处理,我们将其转换为整数,并测度方差:

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\customer.csv’)

frame[‘yearly_income’] = frame[‘yearly_income’].str.split(” “).str[0].str.replace(‘[^0-9]’, ”)

frame[‘yearly_income_new’] = frame[‘yearly_income’].astype(int)

print(frame[‘yearly_income_new’].describe())

你会发现原始的年收入列方差非常大。方差大意味着数据分布非常分散,而对于会员卡等级预测而言,我们其实只有四种类别,显然过大的数据波动影响反而不利。

如何减少方差呢?我们不妨做个转换,将不同的年收入映射到几个区间中:

frame[‘yearly_income_new’] = frame[‘yearly_income_new’] // 30

print(frame[‘yearly_income_new’].describe())

这里的双斜杠表示整除,原始数据中最大最小数值差距为140,现在转换为5,方差极大的减少。

我们利用这个减少方差的新年收入来替换下过去的决策树方法:

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
frame['yearly_income_new'] = frame['yearly_income'].astype(int)
frame['yearly_income_new'] = frame['yearly_income_new'] // 30
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income_new", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
clf = RandomForestClassifier()
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

你会发现提高了接近0.4个百分点。

我们再举一个增加数据列的例子。

关于时间列,该数据有两个,一个是顾客生日,一个开卡的时间,由此我们可以计算出一个开卡年龄:

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\customer.csv’)

frame[‘age’] = pd.to_datetime(frame[‘date_accnt_opened’]).dt.year – pd.to_datetime(frame[‘birthdate’]).dt.year

frame[‘age’] = frame[‘age’] // 20

print(frame[‘age’].describe())

考虑到过细的年龄数据即导致很大的方差,实际意义也不大,因此我们通过整除20映射到了年龄段。

增加此数据列,观察预测结果:

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['age'] = pd.to_datetime(frame['date_accnt_opened']).dt.year - pd.to_datetime(frame['birthdate']).dt.year
frame['age'] = frame['age'] // 20
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["age", "yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
clf = RandomForestClassifier()
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

实际效果并不好,当然这里我们只是演示一种创建列的可行方法。不过即使如此,我们还是可以改进。

比如我们认为中青年年龄段的消费能力最强,于是我们再次调整年龄数据列:

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['age'] = pd.to_datetime(frame['date_accnt_opened']).dt.year - pd.to_datetime(frame['birthdate']).dt.year
frame['age'] = frame['age'] // 20
frame.loc[frame['age'] >= 3, 'age'] = 1
frame.loc[frame['age'] <= 1, 'age'] = 1
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["age", "yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True,  right_index=True)
X = frame_full
clf = RandomForestClassifier()
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

这里的想法是认为过低的年龄(小于20岁)和过高的年龄(大于60岁)没有区别,消费能力都有限,于是调整为统一的年龄段特征。从结果来看,比刚才的做法要提高1个百分点。当然,这里只是演示如何通过新增列来扩展数据的方法,事实上,年龄列效果非常不好。后面会有介绍。真实的考虑往往还需要更多结合应用的特点来增加真正有价值的数据列,在一些基于时间序列的应用中还可以通过不同行之间的时序关系得到更多的特征信息,如增长率等。

第三,数据选择。一般而言,选择特征数量的增加会导致计算复杂度的指数级增长,因此比较常见的处理方法是首先尝试表现较好的单个特征,在根据情况酌情增加。

那么如何选择这些数据呢?

我们可以根据数据之间的相关性来进行选择。我们就以前面我们曾经使用过的几个数据列,比较下与会员卡列的相关性:

import pandas as pd
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from sklearn.preprocessing import OneHotEncoder
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['age'] = pd.to_datetime(frame['date_accnt_opened']).dt.year - pd.to_datetime(frame['birthdate']).dt.year
frame['age'] = frame['age'] // 20
frame.loc[frame['age'] >= 3, 'age'] = 1
frame.loc[frame['age'] <= 1, 'age'] = 1
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["age", "yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
y = frame["member_card"]
transformer = SelectKBest(score_func=chi2, k='all')
Xt_chi2 = transformer.fit_transform(X, y)
print(transformer.scores_)

这里直到X的生成都是前面介绍过的方法,y为会员卡比较列,我们调用列SelectKBest类型返回最佳相关特征,chi2的参数值表示采用卡方检验,all参数值表示返回所有特征情况。通过运算后,结果可以看出,年龄字段的价值非常小,甚至起到相反的作用,而最大相关度的是年收入和汽车拥有数量。

我们还可以使用主成分分析算法来寻找最小的有效特征集合,对于相关的数据就可以无需全部利用,同时还能保证整体数据有效性不变。这里我们去除了年龄列。

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
import numpy as np
from sklearn.decomposition import PCA

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
pca = PCA(n_components=2)
Xd = pca.fit_transform(X)
np.set_printoptions(precision=3, suppress=True)
print(pca.explained_variance_ratio_)
print(Xd)

我们定义了只生成两个2成分,并将原始含有6列的X特征最终转化为2列特征,其实还可以通过输出观察到2列各自的贡献度,我们以3位小数限制输出。当然,这里的2列绝不是简单的从原始6列中抽取2列,而是综合各列加权汇总而成的两个特征。

再以这两个特征,虽然特征只有2列,但是已经很取得很好的预测效果。

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
import numpy as np
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
pca = PCA(n_components=2)
Xd = pca.fit_transform(X)
clf = RandomForestClassifier()
scores = cross_val_score(clf, Xd, y, scoring='accuracy')
print(np.mean(scores))

Python大数据分析3:决策树与随机森林(会员卡预测)

所谓决策树,它也是一种分类方法,我们可以认为就是根据一些特征条件,来对结果类别做出判断,比如结合客户属性特征,看看是否适合办理信用卡等等。此时的类别就是该不该办理信用卡。

这次我们结合一个foodmart 2000数据集,来看看顾客会员卡的等级预测问题。这是一个跨国食品超市的数据,其中提供了1万多条会员卡顾客数据。其中提供了27个相关特征数据,如姓名、地址、收入、教育情况等,还提供了一个会员卡数据列,其中有金卡、银卡、铜卡和普通卡四种类型。

决策树分类方法和前面的方法很相似,关键是先得到所需的很多特征,特征的选择很关键,甚至有效准确的特征比算法还重要。作为练习,我们先找一些重要的特征吧!标准分类模型都是默认采用数值型特征。

因此我们先选择选择了三个数值型特征,分别是小孩数、汽车数和年收入。

其中年收入需要处理下才能使用,即转换为真正的数值。大家会注意到这是个范围,我们可以先直接提取所有的数字:

这里我们采用了是将每个年收入利用str获取字符串表示,并进一步调用replace函数替换所有非数字字符,这里我们采取了一种被称为正则表达式的方法,0-9表示10个数字,^表示不是,显然这样更为灵活,将其全部替换为空字符,即换成没有:

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\customer.csv’)

print(frame[‘yearly_income’].head(2))

frame[‘yearly_income’] = frame[‘yearly_income’].str.replace(‘[^0-9]’, ”)

print(frame[‘yearly_income’].head(2))

从结果来看,可以通过这个整数来间接表达收入的规模。

当然,也可以采取另外一种方法,比如我们只取目前年收入范围的下限,这个是通过先截取到空格为止的前面的所有字符,然后再对齐进行替换:

frame[‘yearly_income’] = frame[‘yearly_income’].str.split(” “).str[0].str.replace(‘[^0-9]’, ”)

print(frame[‘yearly_income’].head(2))

好了,数据到此整理完毕。

代码其实不复杂,还和以前差不多:

import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
clf = DecisionTreeClassifier()
X = frame[["yearly_income", "total_children", "num_cars_owned"]]
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

这里的主要区别在于:第一,将三个数值列为特征数据,而会员卡列作为预测列;第二,使用了DecisionTreeClassifier分类器。

一般而言,如果能够引入更多的相关特征,决策树分类器的效果很更好一些,比如我们认为受教育程度和职业往往也与会员等级关系密切,于是我们继续增加了教育特征:

X = frame[[“yearly_income”, “total_children”, “num_cars_owned”, “education”]]

scores = cross_val_score(clf, X, y, scoring=’accuracy’)

print(np.mean(scores))

但是我们发现出错了。原因很简单,字符类型的列不能直接参与各种分类运算,因此必须将其转换为数值。

转换方法可以利用LabelEncoder来实现,我们先来看看转换的效果:

import pandas as pd
from sklearn.preprocessing import LabelEncoder

frame = pd.read_csv('C:\\temp\\customer.csv')
encoding = LabelEncoder()
encoding.fit(frame["education"])
education_new = encoding.transform(frame["education"])
print(frame["education"].values)
print(education_new)

创建完LabelEncoder编码器后,首先现以当前受教育程度列训练这个编码器,其实就是让它知道这个列有几种不同的字符串,然后再让其转换这个列。

从结果来看,其实就是对于不同的字符串分配不同的数字,相同的字符串使用同一个数字来表示。

然后我们就可以将这个新的教育列加进去:

import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
encoding = LabelEncoder()
encoding.fit(frame["education"])
frame['education_new'] = encoding.transform(frame["education"])
y = frame["member_card"]
clf = DecisionTreeClassifier()
X = frame[["yearly_income", "total_children", "num_cars_owned", "education_new"]]
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

这里首先再DataFrame中建立一个education_new列,并将生成的新教育列赋值过来,既可在后面添加该列直接运算。

这里大家可以注意到了,效果似乎变差了,这是为什么呢?当然特征的选择有可能有问题,但是这里的问题更应该表现在这个数值转换过程。因此LabelEncoder是将字符转为整数,不同的整数虽然可以彼此区分,但是彼此数值的大小却似乎体现一种联系。

比如这里转换的结果我们可以看到,从数值来看,4和3更接近而相似,但是和2更不相似。然而,这其实是不对的,因为4对应的字符串并不和3对应的字符串更相似,似乎和2更相似些。LabelEncoder只是随意分配,并没有考虑字符串本身的相似度,数字只是区分。但是,这种整数却对分类方法产生一定的误导。

因此有效的方法应该使用独热编码。什么是独热编码,我们先看看:

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
encoding = OneHotEncoder()
print(frame["education"].values)
newData = encoding.fit_transform(np.vstack(frame["education"].values)).todense()
print(newData)

我们先看结果,受教育程度列有几种字符串,就会有几个列,每个列对应一种字符串。因此,通过对应的列设置为1可以表示当前行对应的列取这个值。我们明显看到对应关系,有5个列。

这种编码设计的好处在于数值既区分的彼此,也不会产生前面那种数值相似性的问题。

这里略有复杂的地方在于里面需要一种转换,这里简单说明下,

import pandas as pd

import numpy as np

frame = pd.read_csv(‘C:\\temp\\customer.csv’)

print(frame[“education”].values)

print(np.vstack(frame[“education”].values))

这是numpy计算包提供的一种转换功能,可以将一维数值序列变成二维矩阵,里面只有一列,就是刚才这个一维数值序列。

之所以这里需要这种转换,是因为只有有了这个二维矩阵,里面的每一行正好对应原始数据的每一行,后面才能生成含有多个热独编码的新列。

为了能后续进行统一的处理,需要我们首先将顾客DataFrame和这个对应的热度编码合并成一个DataFrame:

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["education"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
print(frame_full)

这里首先将编码矩阵直接转换为DataFrame,然后和顾客DataFrame中所需的列连接下,这里的连接条件就是按照索引行一一对应连接即可,请大家注意相应的属性设置写法。

最后就可以连成一起了!

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["education"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
clf = DecisionTreeClassifier()
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

结果比刚才的LabelEncoder编码改进一些,但是依然低于最初我们没有引入受教育程度的情况,这可能也说明这个列其实意义有限。

那我们可以尝试增加其他列试一试!

newData = encoding.fit_transform(np.vstack(frame[“marital_status”].values)).todense()

我们只需更换这一句里的列,比如是否已婚,就会发现结果提高了,准确度达到77.4%。

有时,甚至需要我们去创建、补充一些新的列,来更好的得到预测模型,很多情况下都需要考虑特定的应用场景特点。大家可以自行多去了解观察。

对于决策树而言,它是利用这些我们选择的特征来进行相应的判断,同时行的选择也会影响效果,因此有时模型的有效性很大程度上取决于我们选择的数据。因此,我们如果根据不同的特征组合和行组合建立不同的多棵决策树,用它们分别进行预测,再根据少数服从多数的原则从多个预测结果中选择最终预测结果,这一定更有把握。

这其实就是随机森林的工作原理。

我们只替换了DecisionTreeClassifier为RandomForestClassifier:

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True,
                      right_index=True)
X = frame_full
clf = RandomForestClassifier()
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

效果略有提升。

随机森林还能调节各种参数,甚至还能允许通过算法自行测试得到最优的参数设置:

import pandas as pd
from sklearn.preprocessing import OneHotEncoder
import numpy as np
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier

frame = pd.read_csv('C:\\temp\\customer.csv')
frame['yearly_income'] = frame['yearly_income'].str.split(" ").str[0].str.replace('[^0-9]', '')
y = frame["member_card"]
encoding = OneHotEncoder()
newData = encoding.fit_transform(np.vstack(frame["marital_status"].values)).todense()
frame_new = pd.DataFrame(newData)
frame_full = pd.merge(frame[["yearly_income", "total_children", "num_cars_owned"]], frame_new, left_index=True, right_index=True)
X = frame_full
parameter_space = {
    "max_features": [2, 4, 'auto'],
    "n_estimators": [100, ],
    "criterion": ["gini", "entropy"],
    "min_samples_leaf": [2, 4, 6],
}
clf = RandomForestClassifier()
grid = GridSearchCV(clf, parameter_space)
grid.fit(X, y)
print(grid.best_estimator_)
print(grid.best_score_)

这里我们设置了一些参数及其数值的组合,允许利用GridSearchCV类型去组合搭配,并比较出最好的组合,我们可以通过best_estimator_得到参数设置,通过best_score_得到现在的最优值。

此时即可将best_estimator_得到参数设置回填回去:

clf = RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None,
                             criterion='gini', max_depth=None, max_features=2,
                             max_leaf_nodes=None, max_samples=None,
                             min_impurity_decrease=0.0, min_impurity_split=None,
                             min_samples_leaf=4, min_samples_split=2,
                             min_weight_fraction_leaf=0.0, n_estimators=100,
                             n_jobs=None, oob_score=False, random_state=14,
                             verbose=0, warm_start=False, )
scores = cross_val_score(clf, X, y, scoring='accuracy')
print(np.mean(scores))

此时,已经达到目前我们所做的最好准确度。

请注意,这里有一个参数random_state很有趣,事实上,每次运行由于选择的训练集和测试集可能都不一样,因此总是有些小小的差异,通过这个参数设定,可以将选择的随机化固定下来,从而使得每次运行结果是一定的,更便于对比分析。

Python大数据分析2:数据分析过程的完善(企业欺诈识别)

我们继续来完善上述分析过程。上次我们谈到的方法,其实是一种最为直观简化的方法,主要是方便大家理解如何进行利用机器学习方法来进行数据分类。但是,真正在应用中,我们还需要注意很多问题,我们仍然按照处理流程分别来看下:

首先看下数据预处理。上次我们已经说明了数据存在一定的错误,这个其实很正常,我们获取的数据不仅可能存在的一些诸如遗漏、误写等明显错误,而且还可能存在着一些与应用分析相关的错误,比如对于分类算法,通常还需要对非数值型数据进行数值型的转换。

比如上次企业审计数据存在部分非数值型数据,我们来看看如何使用Python来处理。

import numpy as np

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\audit_risk.csv’)

results = frame.applymap(np.isreal)

print(results)

这里,我们使用了applymap函数,它可以将整个DataFrame所有元素都应用下参数中指定的函数。其中在判断是否为数值时,我们利用了numpy库中的isreal函数,这个需要我们仍然导入numpy库。applymap最终返回一个和原始DataFrame一样大小的新DataFrame,只是其中的每个数值都被处理了下。

由于DataFrame在转换列类型时,通常都是以列为单位的,也就是说,一旦有一列含有非数值元素,它就会将此列全部设置为非数值型,如字符型。所以我们能看到这里有一个列LOCATION_ID,就是这种情况。

显然,此时为了效果,可以只显示列名即可,0表示列,1表示行:

results = frame.applymap(np.isreal).all(0)

print(results)

此时all为0的设置就表示只以列显示结果,并且列中所有元素都满足,该列才命中。

我们已经知道了LOCATION_ID列存在非数值元素。

如果列很多,更有效的方法是只显示有问题的列,即isreal为假的列:

print(results[(results == False)])

接下来如何处理这些异常数据呢?这里介绍一种简单常见的方法:

frame[‘LOCATION_ID’] = pd.to_numeric(frame[‘LOCATION_ID’], errors=’coerce’)

results = frame.applymap(np.isreal).all(0)

print(results[(results == False)])

这里使用to_numeric将其强制转换为数值,同时对于异常数据,则使用空值来替换,这个是通过errors参数为强制来实现的。

从结果来看,现在已经全部都是数值了!

对于空值,也是常见的问题。

我们先查找下空值:

import numpy as np

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\audit_risk.csv’)

frame[‘LOCATION_ID’] = pd.to_numeric(frame[‘LOCATION_ID’], errors=’coerce’)

results = frame.isnull()

print(results)

和刚才一样,也返回一个和原始DataFrame一样大小的新DataFrame,只是其中的每个元素都是真假表示是否为空值。

显然,需要再次查询下定位:

results = frame.isnull().any(0)

print(results[results == True])

这里any表示该列只要有任意一个为空则命中。从结果来看,存在两列,一列是我们替换非数值的LOCATION_ID,还有一个是新的Money_Value。

究竟是那几行呢?

results = frame.isnull().any(1)

print(results[results == True])

只要把any的0换为1,表示行,就能看到。此时凭借着行列就可以手工去修改下数据。

我们这里对于空值都采取了使用0来填充。

frame = frame.fillna(0)

results = frame.isnull().any(1)

print(results[results == True])

从结果来看,已经全部替换完毕。

这是完整代码:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
import numpy as np

frame = pd.read_csv('C:\\temp\\audit_risk.csv')
frame['LOCATION_ID'] = pd.to_numeric(frame['LOCATION_ID'], errors='coerce')
frame = frame.fillna(0)
y = frame[frame.columns[len(frame.columns) - 1]]
frame.drop(frame.columns[len(frame.columns) - 1], axis=1, inplace=True)
X = frame
X_train, X_test, y_train, y_test = train_test_split(X, y)
estimator = KNeighborsClassifier()
estimator.fit(X_train, y_train)
y_predicted = estimator.predict(X_test)
print(np.mean(y_test == y_predicted))

当然,除了使用这种0值填充方法外,我们也可以使用更为灵活的填充方法,比如就拿空值所在列的平均值来填充,方法其实很简单:

from sklearn.impute import SimpleImputer

imp = SimpleImputer(strategy=’mean’)

X = imp.fit_transform(X)

只需将刚才填充X的语句换掉即可,这里看得出是使用mean平均值来填充。

当然效果可能会因数据、算法和填充策略而各不一样。这里就需要大家多比较。

除了这些数据问题外,还有一种常见的数据问题,称之为数据规范化问题。也就是说,不同列的数据呈现出不同的波动幅度和取值范围,因此在进行各种算法比较中,这些数据本身的数值差异会对算法产生一些不利影响。因此,我们常常还需要进行必要的规范化。

做法其实很简单:

from sklearn.preprocessing import StandardScaler

X = StandardScaler().fit_transform(X)

只需在模型数据划分前,利用合适的规范化工具处理下整体特征数据处理下即可。

这里结果变优了。当然,需要注意的是,盲目使用未必一定最优。

事实上,有时我们还需针对不同的场景和数据,灵活选择更多的规范化方法。

大家有没有注意到,即使对于同样的代码,每次运行效果都并不完全一样。

这里其实反映了一个重要的问题,那就是数据的选择。由于算法自动切分训练集和测试集,数据的划分不一样,结果就可能不一样,因此更为合理的方法是进行多次验证,这种也被称为交叉验证。比如我们将训练集和测试集交换:

estimator = KNeighborsClassifier()

estimator.fit(X_test, y_test)

y_predicted = estimator.predict(X_train)

print(np.mean(y_train == y_predicted))

效果要差些,因为默认的测试集数量少于训练集,训练效果不好。

当然无需我们自己来手动的分组来试,我们可以利用sklearn中的一些非常方便的方法。

from sklearn.model_selection import cross_val_score

estimator = KNeighborsClassifier()

scores = cross_val_score(estimator, X, y, scoring=’accuracy’)

print(np.mean(scores))

此时,默认分为5组,挨次把其中的一组作为测试集。代码反而更简单了,创建完分类器后,直接调用交叉验证方法,给出分类器模型、训练集、测试集和评价指标,直接就可以输出最终的结果。

事实上,这个结果没有这么高,大约为91%左右。

也可以设置交叉分组的次数:

scores = cross_val_score(estimator, X, y, scoring=’accuracy’, cv=10)

print(np.mean(scores))

这里最为极端的一个例子是自己验证自己,显然这并没有意义:

estimator = KNeighborsClassifier(n_neighbors=1)

estimator.fit(X_train, y_train)

y_predicted = estimator.predict(X_train)

print(np.mean(y_train == y_predicted))

这也说明训练集和测试集分开的必要性。

对于同样的数据,这些模型都是固定的算法,那么究竟有哪些影响结果的因素呢?刚才我们说的都是训练集和测试集的划分和选择问题。其实还有一种可能,即模型参数,不同的参数选择,效果也会不一样,因此称之为参数调优。这些参数也被称为超参数。

比如这里的分类算法而言,究竟邻居数量选择几个是大有学问的,我们大胆的试一试使用一个1个邻居参与运算,看看效果。

estimator = KNeighborsClassifier(n_neighbors=1)

scores = cross_val_score(estimator, X, y, scoring=’accuracy’)

print(np.mean(scores))

你会发现结果变高了一点点。

事实上,默认邻居数量为5。这里是将邻居从1到20全部设置的最终准确度展示结果。可以看出结果呈现出以1和10为最高点,其余数量都依次递减的特点。

这种调参的过程显然也比较麻烦,因此可以考虑使用一些更为简单有效的模型,比如高斯朴素贝叶斯分类,速度很快,而且不需要选择超参数,所以通常很适合作为初步分类手段。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
import numpy as np
from sklearn.model_selection import cross_val_score

frame = pd.read_csv('C:\\temp\\audit_risk.csv')
frame['LOCATION_ID'] = pd.to_numeric(frame['LOCATION_ID'], errors='coerce')
frame = frame.fillna(0)
y = frame[frame.columns[len(frame.columns) - 1]]
frame.drop(frame.columns[len(frame.columns) - 1], axis=1, inplace=True)
X = frame
X_train, X_test, y_train, y_test = train_test_split(X, y)
estimator = GaussianNB()
scores = cross_val_score(estimator, X, y, scoring='accuracy')
print(np.mean(scores))

其中我们仅仅替换了模型,其他没有做任何改变,结果却获得了很好的提升,而且无需设置超参数。

因此,整个机器学习的分析方法可以总结下:

第一,获取数据;

第二,划分特征数据和预测数据;

第三,数据预处理,包括必要的数据替换、填充和规范化(一般不对预测数据做处理);

第四,划分训练集和测试集;

第五,选择合适的模型并训练模型;

第六,预测结果和评估。

有时候,这个过程虽然简单,但是写法需要一步一步,很繁琐,因此可以通过一种称之为管道的方式组装起来,更为方便:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

frame = pd.read_csv('C:\\temp\\audit_risk.csv')
frame['LOCATION_ID'] = pd.to_numeric(frame['LOCATION_ID'], errors='coerce')
frame = frame.fillna(0)
y = frame[frame.columns[len(frame.columns) - 1]]
frame.drop(frame.columns[len(frame.columns) - 1], axis=1, inplace=True)
X = frame
X_train, X_test, y_train, y_test = train_test_split(X, y)
pipeline = Pipeline([('scale', StandardScaler()),
                     ('predict', KNeighborsClassifier())])
scores = cross_val_score(pipeline, X, y, scoring='accuracy')
print(scores)

这里的pipeline封装了规范化方法、模型方法等,直接使用非常方便。

Python大数据分析1:数据分类分析(企业欺诈识别)

数据分类是一种机器学习的数据预测方法。

在这里,我们谈到的机器学习。大家可能会觉得有点畏惧,觉得是不是很难。我们首先需要对它有个简单的了解:第一,机器学习是一些计算机分析方法的统称,虽然很难,但是Python已经提供了很多很好用的库,我们只需学会如何使用即可,正如相机拍照原理和过程很复杂,但是我们只需按动那个快门键;第二,机器学习方法不同于前面我们介绍的一些基于现有数据进行总结性分析的方法,这些方法或者通过表格,或者通过可视化,提供给大家一种了解现有数据的分析途径。机器学习分析方法更强调智能性,甚至可以对未知数据进行预测分析,这些能力非常强大。

这次我们就先看看分类。在这里,数据分类有着明确的规定,即对于一组数据,我们事先对它们进行一种分类标注,然后通过一些诸如机器学习等分析方法,了解这种分类背后的数据规律,并由该机器学习分析方法自动预测新数据的类别。

我们从一个例子开始。加利福尼亚大学提供了一组免费的公开数据集合,其中提供了各种分析功能适用的数据:其中有三百多种分类目的的数据,我们选择分类数据,进一步选择其中的企业审计数据(Audit Data),此时点击Download后面的Data Folder数据文件夹,即可在打开的界面中选择Audit_Data.zip数据文件即可。

这个数据2015年至2016年间一些公司非机密数据,根据诸如环境状况报告、利润价值记录等信息,预测公司的欺诈可能性,共计26个数据列。这个可能性主要通过最后一列的Risk可以看出,为1表示存在风险,为0表示不存在风险。

接下来,我们需要解释一下如何进行分类分析。和所有其他机器学习方法差不多,都是一种常见的流程。我们接来下就按照这个流程来说明下:

首先是数据获取加载。

我们还是通过pandas读取数据:

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\audit_risk.csv’)

print(frame)

第二步是数据预处理。不同的机器学习方法可能对数据有着不同的要求,比如一般的分类方法,要求所有参与计算的列必须是数值型,同时还可能要求数据不能有缺失的空值。

事实上,这个数据是有些小问题的。

首先我们可以观察下有无非数值型数据。具体Python处理方法我们后面专门来讲,这里我们就不做过多介绍。不过即使如此,我们也可以使用最为常见的Excel来查找下。

在Excel中按下Ctrl+A选中全部记录,按下Ctrl后再点击下第一行,你会发现选择内容去除了第一行的标题,在Excel中直接按下Ctrl+F查找,即可在选择的数据内容中查找,在定位中选择常量下的文本,去除其他选择项目,点击定位即可。此时可以看到已有三块数据被选中,为灰色,建议直接替换为0。同样的方法,在定位中选择空值,也可以快速看到存在的空值记录,建议使用0填充。到此为此,数据预处理手工完成。

第三步是数据划分。这里包括两个操作,一是数据列的划分,将现有数据分为特征列和预测列,也就是说,你准备按照什么数据进行分类,即特征列,这里应该就是除了最后一列外所有的企业数据特征列。分好的类在哪个列标记,即类别类,也是将来准备预测类别的列,因此也可以称之为预测列。这里就是最后一列,取值为0或者1的Risk。

写成代码的形式就是:

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\audit_risk.csv’, header=0)

y = frame[frame.columns[len(frame.columns) – 1]]

frame.drop(frame.columns[len(frame.columns) – 1], axis=1, inplace=True)

X = frame

print(X)

这里首先取出最后一列,按照习惯,预测列名称通常为小写的y。其中这里我们没有使用列名的方法,故意使用了另外一种方法,即按照最后一列的序号来取出相应的列,根据当前列的个数减一可以得到最后一列的序号,因为从零开始表示第一列。然后根据这个序号得到相应的列,这种用法在没有列名的时候比较灵活。然后再删除最后一列,这里没有保存Frame,而且直接通过inplace参数直接在原有DataFrame上删除,axis为1表示以列为单位操作,而第一个参数正是最后一列的序号。按照习惯,特征列名称通常为大写的X。

第二个数据行的划分,主要目的是分为训练集和测试集合。所谓训练集合,是指使用哪些数据来训练分类算法。这些分类模算法必须在一些给定的数据中去训练,或者说去学习,才能自动获取最能反映分类的规则,并保存到分类模型中。一般会把整体数据的大部分作为训练集合,不能太小,否则训练效果会较差。所谓测试集合,主要是用来评估分类算法,看看拿刚才的模型来预测下,比如这里预测下分类结果,看看和已知的结果是否一致,显然越一致算法越好。通常这部分数据较少。到了真实应用场景下,就可以拿真正想预测的数据作为测试集合。

看起来很复杂,其实很简单,我们再次使用一个著名的Python库,叫sklearn,它是目前非常著名的一个实现各种机器学习方法的库。还像以前一样导入到项目,代码为:

import pandas as pd
from sklearn.model_selection import train_test_split

frame = pd.read_csv('C:\\temp\\audit_risk.csv', header=0)
y = frame[frame.columns[len(frame.columns) - 1]]
frame.drop(frame.columns[len(frame.columns) - 1], axis=1, inplace=True)
X = frame

X_train, X_test, y_train, y_test = train_test_split(X, y)
print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)

其中首先导入sklearn中专门用于划分训练集和测试集的train_test_split类型,自动获得四块内容,正好对于特征列和预测列、训练集和测试集的四种组合。

这里输出了下大小,正好看出,所有X相关的特征数据都是26列,所有训练和测试的数据比例划分是3:1,大家看懂了这个结果吗?

也可以通过设置test_size改变默认的分隔设置:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1)

test_size默认为0.25,表示测试集的比例。

好了,到了最关键的一步,即第四步,数据训练。就是根据上述分好的数据,利用训练集合相关的两块数据来计算、来学习,得到分类模型。

代码为:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier

frame = pd.read_csv('C:\\temp\\audit_risk.csv', header=0)
y = frame[frame.columns[len(frame.columns) - 1]]
frame.drop(frame.columns[len(frame.columns) - 1], axis=1, inplace=True)
X = frame

X_train, X_test, y_train, y_test = train_test_split(X, y)
estimator = KNeighborsClassifier()
estimator.fit(X_train, y_train)

这里首先需要选择一个分类算法,我们选择了k近邻分类算法,生成一个分类器变量,记得此时需要导入这个分类算法。具体分类怎么实现的,我们无需过问,其实有很多不同的算法,后面我们还可以做出选择调整。

然后调用这个分类器变量的fit方法,里面正好就是训练集合的两块数据。

第五步,就是最后一步,效果评估。一般就是使用模型预测下,比如这里的分类预测,然后看看准确度。

代码为:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
import numpy as np

frame = pd.read_csv('C:\\temp\\audit_risk.csv', header=0)
y = frame[frame.columns[len(frame.columns) - 1]]
frame.drop(frame.columns[len(frame.columns) - 1], axis=1, inplace=True)
X = frame

X_train, X_test, y_train, y_test = train_test_split(X, y)
estimator = KNeighborsClassifier()
estimator.fit(X_train, y_train)
y_predicted = estimator.predict(X_test)
print(np.mean(y_test == y_predicted))

这里仍然分类器的predict方法直接根据测试集的特征数据来预测。

如果进行效果比较呢?这里我们也无需直接去做,再次使用一个著名的科学计算库numpy,也记得导入,并在项目代码中导入,即可利用它的mean,即平均值,我们只需将预测的结果y_predicted和已知的y_test以是否相等作为条件,看看正确的占比有多少。此时,mean这个方法的参数是一个由真假组成的列表,它会自动计算True在其中的比例,作为平均值。这个预测结果的准确度还是挺高的!95%!

为了好看,我们可以再次改变下输出的格式:

print(“准确度为:{:.1f}%”.format(np.mean(y_test == y_predicted) * 100))

这里使用了字符串的格式化输出功能,其中整个字符串都会直接输出,而花括号括起来的部分,会以format函数中的结果来展示,而花括号里的内容就表示按照一位小数输出。

Python大数据分析9:案例-谁是一百年前南方小镇的社交明星?

这次我们根据一个有趣的数据,来看看如何使用网络可视化分析方法。网络可视化分析方法可以让我们以一种全新的眼光来看待平淡无奇的数据,并找到新的发现。

一些社会学家和种族学家在20世纪30年代做了一个很小的数据实验,实验目标是描绘出美国南部一个小镇一组女士的社交结构关系,他们使用的数据是报纸上公开发表的数据,共有18位女士参加了14个不同的社交活动。接近100年过去了,我们今年所能知道的仅仅只有这些数据。这些数据描述了这些女士分别参加了哪些社会活动。

为了方便生成适合网络分析的数据结构,我们需要对数据进行必要的处理。原始数据中有两类数据,一类是女士,一类是活动。一般比较常见的网络图都是由一种类型的数据节点构成,同时由于我们需要知道女士之间的联系,因此需要建立女士之间的关系。

我们可以假设如果两个女士参加一个活动,就意味着两人存在着一个联系,

共同参与的越多,联系应该越紧密。为此我们先来生成女士之间的联系:

# coding:utf-8

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\SouthernLadies.csv’)

frame = pd.merge(frame, frame, left_on=’Activity’, right_on=’Activity’)

print(frame)

这里使用了数据连接的方法。通过将现有的数据自己和自己连接,按照左表和右表活动相等即可连接全部记录。

从结果可以看到,诸如刚才的W01女士和W02女士都建立了连接。这里数据有几个特点:第一,这里至少一半的数据冗余,因为自己会和自己连接,而且W01女士和W02女士连接,就会有W02女士和W01女士连接;第二,确实不同女士可以因为参加同一活动而被连接起来,这种连接会随着共同参与活动的多少而有总数的变化。

我们先处理第一个问题:

frame = frame[frame[‘Lady_x’] < frame[‘Lady_y’]]

print(frame)

这里使用了一个小技巧,我们利用女士号码小于的关系筛选了记录,可以看出但凡等于和大于的都被去除,正好对应自己和自己的连接,以及前后错位的冗余连接。七百多条记录只剩下三百多记录。

再看第二个问题,两位女士共同参加的活动越多,就会形成更多的连接,因此可以对现有连接结果按照连接女士汇总下:

frame = frame[[‘Activity’]].groupby([frame[‘Lady_x’], frame[‘Lady_y’]]).count()

print(frame)

此时,就是按照两个连接女士的号码作为分组条件,汇总统计各自的总数。看的出来,确实存在较大的差异,数值越高,女士的关系应该更为紧密。

此时取出索引的值,生成列表,即可得到网络图所有边的信息:

edges = frame.index.tolist()

print(edges)

对于节点,我们可以在未汇总前直接统计所有女士:

frame = frame.groupby(frame[‘Lady’]).count()

nodes = frame.index.tolist()

print(nodes)

这里仍然利用了分组的方法,取出了分组的所有索引,并生成了一个节点列表,供后续网络图生成时使用。

到此为止,我们已经完成了所需数据内容的整理。这也是数据分析的关键内容,一定要根据分析的目标和方法的特点,有选择的进行数据进行必要的处理,通常这一块是最能体现数据分析特点的地方,不同的整理方法往往也会产生不同的最终分析效果。

将上述两个代码融合下:

# coding:utf-8
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt

frame = pd.read_csv('C:\\temp\\SouthernLadies.csv')
frame1 = frame.groupby(frame['Lady']).count()
nodes = frame1.index.tolist()

frame2 = pd.merge(frame, frame, left_on='Activity', right_on='Activity')
frame2 = frame2[frame2['Lady_x'] < frame2['Lady_y']]
frame2 = frame2[['Activity']].groupby([frame2['Lady_x'], frame2['Lady_y']]).count()
edges = frame2.index.tolist()

g = nx.Graph()
g.add_nodes_from(nodes)
g.add_edges_from(edges)
nx.draw(g)
plt.show()

这里前面的代码就是生成节点列表和边列表,为了反复使用数据,可以看出我们保留了最初的DataFrame,通过存储的frame1和frame2分别计算了节点和边。

最后的部分就是绘图,其实绘图并不复杂,利用导入的networkx,生成图,并添加节点和边,绘制即可,注意networkx不能独立使用,必须配合matplotlib,因此最后不要忘了pyplot的show方法。

这是绘制的结果,显然还需要进一步调整,比如增加必要的标签显示和颜色美化。

nx.draw(g, with_labels=True, node_shape=”s”, alpha=0.9, node_color=’green’,

        node_size=1500, font_size=16, font_weight=”bold”, font_color=’white’)

plt.show()

这些属性大都见名知意,大家可以自行调整。

从中可以看出,有些节点处于一种社交中心的特点,比如W09、W14和W15等。

但是,这里的边都是一样的粗细,无法看出前面我们利用共同参加活动所计算出来的重要性关系。

于是,我们再次使用加权的边来绘制。为此需要做两步:第一步是将权值也保存到列表中:

frame2 = pd.merge(frame, frame, left_on='Activity', right_on='Activity')
frame2 = frame2[frame2['Lady_x'] < frame2['Lady_y']]
frame2 = frame2[['Activity']].groupby([frame2['Lady_x'], frame2['Lady_y']]).count()
weights = frame2['Activity'].tolist()
edges = frame2.index.tolist()

g = nx.Graph()
g.add_nodes_from(nodes)
g.add_edges_from(edges)
nx.draw(g, width=weights, edge_color=weights,
        with_labels=True, node_shape="s", alpha=0.9, node_color='green',
        node_size=1500, font_size=16, font_weight="bold", font_color='white')
plt.show()

首先我们将总数统计列保存为一个列表weights,显然和它和边列表元素个数一样,并且一一对应。然后,我们再draw函数中设置width属性即可表达每条边的宽度和颜色,以体现这个权值。

图中可以看出,W03和W04等节点呈现出比较明显的、与其他节点的强关联性,浅色权值更高,说明她们的好伙伴更多一些,并且隐隐约约还能看到两个高度关联的团体。

为此我们需要将无关的低权值边去除,以便更高的看清结果。

我们在分组后增加一条删除汇总个数较低的边,比如小于4:

frame2 = frame2.drop(index=(frame2.loc[(frame2[‘Activity’] < 4)].index))

weights = frame2[‘Activity’].tolist()

edges = frame2.index.tolist()

从结果来看,虽然更能看到这两个社交团体,但是由于存在很多无关节点,默认布局很不好看。

因此我们需要去除这些已经不再连接边的独立节点。我们先使用一种最为直观的方法,就是从边中直接得到需要显示的点:

# coding:utf-8
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt

frame = pd.read_csv('C:\\temp\\SouthernLadies.csv')
frame2 = pd.merge(frame, frame, left_on='Activity', right_on='Activity')
frame2 = frame2[frame2['Lady_x'] < frame2['Lady_y']]
frame2 = frame2[['Activity']].groupby([frame2['Lady_x'], frame2['Lady_y']]).count()
frame2 = frame2.drop(index=(frame2.loc[(frame2['Activity'] < 4)].index))
weights = frame2['Activity'].tolist()
edges = frame2.index.tolist()

frame2 = frame2.reset_index()
nodes1 = frame2.drop_duplicates('Lady_x')['Lady_x'].tolist()
nodes2 = frame2.drop_duplicates('Lady_y')['Lady_y'].tolist()
nodes = nodes1 + nodes2

g = nx.Graph()
g.add_nodes_from(nodes)
g.add_edges_from(edges)
nx.draw(g, width=weights, edge_color=weights,
        with_labels=True, node_shape="s", alpha=0.9, node_color='green',
        node_size=1500, font_size=16, font_weight="bold", font_color='white')
plt.show()

我们去除了最早的节点生成方法,利用当前索引中已有的节点,通过去除Lady_x和Lady_y重复的元素,转换为列表再拼装,虽然有重复,但是问题不大,得到最终只有这些权值大于等于4的边才有的节点。

进一步调整边权值的阈值为5,可以发现更为明显,两个高度关联的女士社交团体就被识别了出来。

进一步调整边权值的阈值为3,又可以发现这两个团体是如何关联的,看得出来,一号团体的W03、W09,和二号团体的W10、W13和W14,是形成这两个团体联系的关键人物。

当然这样做,效果不错,但是获取点的方法太麻烦了,其实更为有效的方法可以直接从边中获取所有的边和节点:

其中我们直接将索引恢复到原有数据中,形成包括Lady_x、Lady_y和统计列的三列数据。

print(frame2.head(1))

frame2 = frame2.reset_index()

print(frame2.head(1))

这样就可以直接利用这个完整的DataFrame来生成网络图。

这是综合后的代码:

# coding:utf-8
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt

frame = pd.read_csv('C:\\temp\\SouthernLadies.csv')
frame2 = pd.merge(frame, frame, left_on='Activity', right_on='Activity')
frame2 = frame2[frame2['Lady_x'] < frame2['Lady_y']]
frame2 = frame2[['Activity']].groupby([frame2['Lady_x'], frame2['Lady_y']]).count()
frame2 = frame2.drop(index=(frame2.loc[(frame2['Activity'] < 3)].index))
weights = frame2['Activity'].tolist()

frame2 = frame2.reset_index()
edges = frame2.values.tolist()
g = nx.from_pandas_edgelist(frame2, 'Lady_x', 'Lady_y', create_using=nx.Graph())

nx.draw(g, width=weights, edge_color=weights,
        with_labels=True, node_shape="s", alpha=0.9, node_color='green',
        node_size=1500, font_size=16, font_weight="bold", font_color='white')
plt.show()

其中关键的语句就是from_pandas_edgelist方法,里面很清楚的给出边的起始节点列和终止节点列,绘图会自动绘制相关边和点。图形显示和以前没有区别,但是代码更为简单。

最后让我们把布局再美化下:

fig = plt.figure()

nx.draw(g, pos=nx.circular_layout(g),

        width=weights, edge_color=weights,

        with_labels=True, node_shape=”s”, alpha=0.9, node_color=’green’,

        node_size=1500, font_size=16, font_weight=”bold”, font_color=’white’)

fig.set_facecolor(“#000000”)

plt.show()

这里才有了黑色背景,请注意fig获取必须在draw之前进行才有效果,set_facecolor方法设置为黑色,这里颜色还是以前介绍的表示方法,红绿蓝都为0,为纯黑色。

另外,还采取了新的布局模式,circular_layout表示环形布局。

Python大数据分析8:案例-各省的人都往哪儿去?

这次我们结合人口流动的数据来看看我国人口在各省之间的移动特点,并据此从人口流动的角度,来分析下我国各省的相关性。当然,这里主要利用的还是各种pyecharts可视化分析方法。

首先我们加载人口迁移数据:

# coding:utf-8

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\population.csv’, encoding=’GBK’)

print(frame)

该数据有三列,分别是流出省份、流入省份和人口流动数量,共计840条数据。这些数据都是从1985年到1990年共计5年的人口汇总统计数据。

为了更好的了解数据,我们首先对数据按照人口流动数量降序排列输出来观察下:

pd.set_option(‘display.max_rows’, None)

print(frame.sort_values(by=[‘count’], ascending=False))

我们发现,数据呈现快速下降的特点,我们可以选择大于10万的记录即可展示最为主要的流动关系。

有时候,可能会有这样的输出,这种情况并非错误,如果pandas在解析数据时,将最后count理解为字符,那么确实就是这个次序。对于字符而言,首先比较左边第一个字符,谁大就谁大,后面就不再比较,只有左边第一个字符一样,才会比较第二个,仍然按照刚才规则进行。

此时可以考虑强制转换为整数列:

frame[‘count’] = pd.to_numeric(frame[‘count’])

print(frame.sort_values(by=[‘count’], ascending=False))

这里的pandas自带的to_numeric也可以完成字符向数字的转换。

我们首先还是以地图可视化来展示下效果。这里需要展示的是一种省份之间的流向关系,因此需要使用线条来表示:

# coding:utf-8
import pandas as pd
from pyecharts.charts import Geo
from pyecharts import options as opts
from pyecharts.globals import ChartType

frame = pd.read_csv('C:\\temp\\population.csv', encoding='GBK')
results = frame[frame['count'] > 100000][['from', 'to']].values.tolist()
geo = Geo()
geo.add_schema(maptype="china")
geo.add("", results, type_=ChartType.LINES, linestyle_opts=opts.LineStyleOpts(curve=0.2))
geo.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
geo.render()

这里注意几个问题:

一是我们得到了人口流动数量大于10万的记录,并将这些数据设置为显示数据;

二是在图样显示中,我们将类型设置为线条,并通过curve属性将其设置为曲线。

从图中可以看出,这里实际显示的箭头是动态的,四川和黑龙江是人口流出大省,其中北京、上海、广东都是一些人口流入的重要地区。

我们再以另外一个角度来展示分析效果。比如就看流入情况:

# coding:utf-8
import pandas as pd

frame = pd.read_csv('C:\\temp\\population.csv', encoding='GBK')
results = frame[['count']].groupby(frame['to']).sum()
results = results.reset_index()
results = results.values.tolist()
print(results)

以人口流入数量按照省份汇总,形成每个省份的总数据。这里要注意一个问题,由于汇总后省份列变成了索引,因此通过reset_index按索引恢复过来,形成可以直接转换为list的数据格式。

这次我们以热点图的类型展示了结果:

# coding:utf-8
import pandas as pd
from pyecharts.charts import Geo
from pyecharts import options as opts
from pyecharts.globals import ChartType

frame = pd.read_csv('C:\\temp\\population.csv', encoding='GBK')
results = frame[['count']].groupby(frame['to']).sum()
results = results.reset_index()
results = results.values.tolist()
geo = Geo()
geo.add_schema(maptype="china")
geo.add("", results, type_=ChartType.HEATMAP)
geo.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
geo.set_global_opts(visualmap_opts=opts.VisualMapOpts(max_=1300000))
geo.render()

再次看出我们人口流入大省的分布情况,仍然主要集中在华南、华东和华北三块,依次递减。

接下来我们准备换个思路来看这个问题,通过人口在不同省份之间的流入流出,其实也能在一定程度上反映省份的关系,我们这次结合网络图来重新分析下省份之间的关系。

网络图其实需要两个基本数据,一个是节点,一个节点之间的边。为此我们来整理下数据。

首先我们得到边,然后根据边确定节点。考虑到界面显示有限,显示太多边会导致无法看清内容,因此我们做了必要的筛选,

# coding:utf-8

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\population.csv’, encoding=’GBK’)

frame = frame[frame[‘count’] > 50000]

links = frame[[‘from’, ‘to’]].rename(columns={‘from’: ‘source’, ‘to’: ‘target’}).to_dict(orient=’records’)

print(links)

这里我们先过滤到现有人口流动数量不高于5万的省份流动记录。对于pyecharts,它也提供了专门的Graph类型用于绘制网络图。但是它要求对边做出固定格式的定义,从结果来看,是一种字典格式。因此,我们通过调用rename改名将列名换为source和target,这里的名称就表示源和目标,然后利用to_dict生成特定的字典格式。可以从结果看出,里面的信息很明确。

我们再得到节点:

results = frame[[‘count’]].groupby(frame[‘to’]).sum()

results[‘name’] = results.index

results[‘count’] = results[‘count’] / 50000

results = results.rename(columns={‘count’: ‘symbolSize’})

nodes = results.to_dict(orient=’records’)

print(nodes)

这里首先根据流入人口汇总,为方便索引的身份信息也转换为后续的字典内容,因此和以前一样,新增列保存为索引一样的内容,同时,新列名称必须为name,表示节点名称,而汇总的人数作为权值,可以利用节点大小来反映,为此需要将列名改为symbolSize,同时由于数值太大,因此可以考虑适当缩小,这里采取了除以最小数值的方法。

有了上述基础数据,我们就以完成绘制:

# coding:utf-8
import pandas as pd
from pyecharts.charts import Graph

frame = pd.read_csv('C:\\temp\\population.csv', encoding='GBK')
frame = frame[frame['count'] > 50000]
links = frame[['from', 'to']].rename(columns={'from': 'source', 'to': 'target'}).to_dict(orient='records')
results = frame[['count']].groupby(frame['to']).sum()
results['name'] = results.index
results['count'] = results['count'] / 50000
results = results.rename(columns={'count': 'symbolSize'})
nodes = results.to_dict(orient='records')
graph = Graph()
graph.add("", nodes, links)
graph.render()

首先要导入Graph类型,生成Graph类型变量,然后add函数中指定节点和边,第一个参数是标题。

从中可以明显看出,从人口流动关系来看,我国不同省份之间的联系。有几个明显的特征:比如以辽宁为中心的北方各省形成一个相对闭合的关系,四川为人口流动的大省,并通过河北和北方其他几个省市建立了联系,并通过江苏和华东几省也建立了联系。

为了增强效果,可以进一步增加显示范围,同时增加边的样式:

graph = Graph(init_opts=opts.InitOpts(width=”1600px”, height=”800px”))

graph.add(“”, nodes, links, linestyle_opts=opts.LineStyleOpts(width=0.5, curve=0.3, opacity=0.7))

graph.render()

这些都可以自由调整。

也可以改变显示模式,改为圆盘显示:

graph.add(“”, nodes, links, layout=”circular”)

graph.render()

结果展示方式变成了新的模样。不同的效果可以根据显示数据的分析要求来选择。

Python大数据分析7:案例-看看我国各省的GDP

地图绘制也是一种有效的可视化方法,它可以直观的将数据内容通过文本和颜色等不同方式展示在地图上,形成一种有效的呈现方式。

Python地图有很多库可以使用,我们这次使用的是著名的pyecharts,它可以更方便的实现地图的绘制。

为此,我们首先还是需要在设置上导入pyecharts库,具体过程同前面所述一致。

我们先绘制一个简单的地图看看。

from pyecharts.charts import Geo

geo = Geo()

geo.add_schema(maptype=”china”)

geo.render()

首先导入Geo类型,这个类型就是绘制地图的关键类型。创建它的变量后,调用add_schema方法指定地图类型为中国,即可render绘制。

此时会在当前项目中生成一个render.html文件,可以右击选择“在浏览器打开”,选择合适的浏览器即可看到结果。

之所以在浏览器才能查看结果,是因为这个是利用网页方式提供绘图功能。

这个地图已经可以显示动态效果,比如停留某个身份可以变亮,

滚动鼠标滑轮还能缩放地图。

好了,我们接下来准备数据。关于各省的GDP数据,可以考虑使用中国国家统计局的官方数据。在地区数据中,可以选择分省年度数据。

在地区数据中,可以选择全部地区。

此时登录即可免费下载,可以选择CSV格式。

下载数据的格式比较多,因此建议大家首先先简单处理下,将无关的一些格式信息去除。比如这里的头三行,

还有最后几行,都可以删除,形成标准的二维数据表格。

我们首先还是利用DataFrame读取下数据。

# coding:utf-8

import pandas as pd

frame = pd.read_csv(‘C:\\temp\\分省年度数据.csv’, encoding=’GBK’)

print(frame)

这里显示的是10年的数据。

我们读取其中最近一年2019年的数据,这里要注意一个问题,那就是pyecharts绘制地图需要一个二维列表结构的数据,因此我们需要读取地区和年份后,通过tolist方法转换为对应的格式:

print(frame[[‘地区’, ‘2019年’]].values.tolist())

现在就可以将其绘制在地图上:

# coding:utf-8
import pandas as pd
from pyecharts.charts import Geo

frame = pd.read_csv('C:\\temp\\分省年度数据.csv', encoding='GBK')
geo = Geo()
geo.add_schema(maptype="china")
geo.add("", frame[['地区', '2019年']].values.tolist())
geo.render()

这里的add函数就是增加数据,第一个参数表示图表名称,我们留空,第二个就是数据内容。这其实是把刚才的两段内容结合在了一起,但是报错了。什么错误呢?其实很简单,地区名称对应不上,原因在于pyecharts中对部分省份的标注与国家统计局不一致,比如“广西壮族自治区”应该改为“广西省”等等。

这里其实只需修改三个省份数据即可。大家可以自行根据错误提示进行修改,这也说明数据分析之前数据整理是非常必要的环节。

再次运行下,就可以在生成的网页中看到结果,

也可以刷新下这个网页来看。中心点大约是省会的位置。

这个网页具有较强的交互性,移动和悬浮鼠标都可以看到不同的变化。

默认情况下,地图上显示的数据内容过多。我们可以酌情去除。

from pyecharts import options as opts

geo.set_series_opts(label_opts=opts.LabelOpts(is_show=False))

geo.render()

这个设置就是表示将当前标签上的数据内容不予显示。这里不要忘记导入options类型,在pyecharts中,图表的一切皆通过options来修饰调整。

但是这时无法看出这些不同数据的直观特点,因此我们可以通过添加颜色来标注数据的大小:

geo.set_global_opts(visualmap_opts=opts.VisualMapOpts(max_=110000))

geo.render()

这里面的max_参数就表示最大值。

由于这些数据分布并不平均,甚至可以自己来定义不同的颜色区间:

geo.set_global_opts(visualmap_opts=opts.VisualMapOpts(
    is_piecewise=True,
    pieces=[
        {"min": 0, "max": 10000, "label": "1", "color": "blue"},
        {"min": 10001, "max": 20000, "label": "2", "color": "cyan"},
        {"min": 20001, "max": 50000, "label": "5", "color": "green"},
        {"min": 50001, "max": 80000, "label": "8", "color": "yellow"},
        {"min": 80001, "max": 100000, "label": "10", "color": "orange"},
        {"min": 100001, "max": 200000, "label": "20", "color": "red"}
    ]
))
geo.render()

这里看起来复杂,其实很简单,就是设定了is_piecewise属性表述分段,同时通过pieces属性后面的字典数据给出了每个段的上下限、标签和颜色。

显然这样更为灵活和定制化。

默认的外观比较简单,我们可以来看下。

比如对于点的形状,就可以通过设置来改变:

geo.add(“”, frame[[‘地区’, ‘2019年’]].values.tolist(), type_=ChartType.EFFECT_SCATTER)

geo.set_series_opts(label_opts=opts.LabelOpts(is_show=False))

geo.set_global_opts(visualmap_opts=opts.VisualMapOpts(max_=110000))

geo.render()

这里的add函数增加了type说明。

这个点会自动呈现变化的动态效果。

甚至可以在add函数中增加颜色样式,设置地图的色彩:

geo.add_schema(maptype=”china”, itemstyle_opts=opts.ItemStyleOpts(color=”#DD0000″, border_color=”#FFFFFF”))

这里面color表示地图背景色,而border_color表示边的颜色。

效果呈现出火热的中国红。大家可能会注意到这里的颜色很奇怪,这其实是计算机表示颜色的一种简单有效方法,

里面有三个部分,每个部分最小为00,最大为FF,这是十六进制的最大值,分别对应红绿蓝,所以边框三色全满,就是白色,而地图背景色绿蓝皆为无,红色淡了一些,形成略浅一些的中国红。大家可以自己大胆的调配看看效果。

也可以使用Map来填充

# coding:utf-8
import pandas as pd
from pyecharts.charts import Map
from pyecharts import options as opts

frame = pd.read_csv('C:\\temp\\分省年度数据2.csv', encoding='GBK')
map = Map()
map.add("", frame[['地区', '2019年']].values.tolist(), "china")
map.set_global_opts(visualmap_opts=opts.VisualMapOpts(max_=110000))
map.render()

这里使用了Map类型,基本用法差不多,主要区别在于add中第三个参数指定地图位置。

该类型使用颜色填充的方式,而不是点的方式,来显示不同数据的情况。

这里要注意一个细节,就是数据需要整理下,Map使用的中国各省份和Geo不一样,需要将全部的省、市、自治区等去掉。于是这里我们保存为“分省年度数据2.csv”文件。