Pandas进阶 期中练习
pandas进阶系列根据datawhale远昊大佬的joyful pandas教程写一些自己的心得和补充
【任务一】企业收入的多样性
【题目描述】一个企业的产业收入多样性可以仿照信息熵的概念来定义收入熵指标:
I
=
−
∑
i
p
(
x
i
)
log
(
p
(
x
i
)
)
\mathrm{I}=-\sum_{\mathrm{i}} \mathrm{p}\left(\mathrm{x}_{\mathrm{i}}\right) \log \left(\mathrm{p}\left(\mathrm{x}_{\mathrm{i}}\right)\right)
I=−i∑p(xi)log(p(xi))
其中 p(xi)
是企业该年某产业收入额占该年所有产业总收入的比重。在company.csv中存有需要计算的企业和年份,在company_data.csv中存有企业、各类收入额和收入年份的信息。现请利用后一张表中的数据,在前一张表中增加一列表示该公司该年份的收入熵指标
I。
【数据下载】链接:https://pan.baidu.com/s/1leZZctxMUSW55kZY5WwgIw 密码:u6fd
【我的思路】
按公司和日期分组,求出收入额与总额的比值,并利用比值求每个项的熵再求出每个组的增益,然后按公司和日期将两表合并
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
df1 = pd.read_csv('../data/Company_1.csv')
df2 = pd.read_csv('../data/company_data.csv')
先看一下两个数据集的结构
df1.head()
证券代码 | 日期 | |
---|---|---|
0 | #000007 | 2014 |
1 | #000403 | 2015 |
2 | #000408 | 2016 |
3 | #000408 | 2017 |
4 | #000426 | 2015 |
df2.head()
证券代码 | 日期 | 收入类型 | 收入额 | |
---|---|---|---|---|
0 | 1 | 2008/12/31 | 1 | 1.084218e+10 |
1 | 1 | 2008/12/31 | 2 | 1.259789e+10 |
2 | 1 | 2008/12/31 | 3 | 1.451312e+10 |
3 | 1 | 2008/12/31 | 4 | 1.063843e+09 |
4 | 1 | 2008/12/31 | 5 | 8.513880e+08 |
结果是要求每个公司每年的收入熵,因此应该考虑在收入表中按企业和年份分组
查看一下分组后有没有出现收入类型重复的情况,因为如果重复的话应该把同类型的收入求和
tmp = df2[['证券代码', '日期', '收入类型']]
tmp.drop_duplicates().equals(tmp)
True
结果表明没有重复
然后按照公式一步一步求,p是每个公司每日期的比例,因此先求出每个小组的收入额的和,再求p,根据p和log p求出每一项的熵,然后求和就是每个组的信息增益,这里因为有0值,求log以后会变成无限大,无限大与0相乘后会变成空值,所以需要对空值处理一下使其为0
df2['total'] = df2.groupby(['证券代码', '日期'])['收入额'].transform('sum')
df2['ratio'] = df2['收入额'] / df2['total']
df2['entropy'] = df2['ratio']*np.log(df2['ratio'])
df2['entropy'].fillna(0, inplace=True)
df2['I'] = -df2.groupby(['证券代码', '日期'])['entropy'].transform('sum')
df2.tail()
证券代码 | 日期 | 收入类型 | 收入额 | total | ratio | entropy | I | |
---|---|---|---|---|---|---|---|---|
964017 | 900957 | 2016/12/31 | 12 | 0.00 | 6.399964e+08 | 0.000000 | 0.000000 | 2.178318 |
964018 | 900957 | 2016/12/31 | 13 | 0.00 | 6.399964e+08 | 0.000000 | 0.000000 | 2.178318 |
964019 | 900957 | 2016/12/31 | 14 | 52072238.97 | 6.399964e+08 | 0.081363 | -0.204127 | 2.178318 |
964020 | 900957 | 2016/12/31 | 15 | 0.00 | 6.399964e+08 | 0.000000 | 0.000000 | 2.178318 |
964021 | 900957 | 2016/12/31 | 16 | 52072238.97 | 6.399964e+08 | 0.081363 | -0.204127 | 2.178318 |
res = df2[['证券代码', '日期', 'I']].drop_duplicates()
res.head()
证券代码 | 日期 | I | |
---|---|---|---|
0 | 1 | 2008/12/31 | 2.085159 |
14 | 1 | 2009/12/31 | 1.671752 |
22 | 1 | 2010/12/31 | 2.108355 |
34 | 1 | 2011/12/31 | 3.150479 |
72 | 1 | 2012/12/31 | 2.718759 |
由于证券代码和日期的格式对不上,所以更改一下格式
res['证券代码'] = res['证券代码'].apply(lambda x: '#' + (6-len(str(x)))*'0' + str(x))
res['日期'] = res['日期'].apply(lambda x: int(str(x)[:4]))
res.head()
证券代码 | 日期 | I | |
---|---|---|---|
0 | #000001 | 2008 | 2.085159 |
14 | #000001 | 2009 | 1.671752 |
22 | #000001 | 2010 | 2.108355 |
34 | #000001 | 2011 | 3.150479 |
72 | #000001 | 2012 | 2.718759 |
这里发现merge的时候需要保持两个表中的列的类型是一样的,原本在公司表中日期是int类型,而公司数据表中日期是object类型,因此在上一步做数据转换的时候要把类型也显式转换一下
最后再按照原表的顺序求信息增益
df1 = df1.merge(res, on=['证券代码', '日期'])
df1.head()
证券代码 | 日期 | I | |
---|---|---|---|
0 | #000007 | 2014 | 3.070462 |
1 | #000403 | 2015 | 2.790585 |
2 | #000408 | 2016 | 2.818541 |
3 | #000426 | 2015 | 3.084266 |
4 | #000426 | 2016 | 2.988900 |
【使用场景】
本道题中提出的信息增益是决策树中常用的算法,用来求结点分裂时的最优分裂特征,最优分裂特征应当是信息增益(比)最高的,类似信息增益作用的算法还有基尼指数,基尼指数相对于信息增益比的优势是其计算更简便,不涉及log计算,而且是二叉树,优化了计算速度,在效率上由于信息增益比,在效果上是信息增益比的一种近似代替。
这里我根据这道题的数据再计算一下基尼指数,并分别计算一下两种算法的效率
基尼指数的公式:
Gini
(
D
)
=
∑
i
=
1
n
p
(
x
i
)
∗
(
1
−
p
(
x
i
)
)
=
1
−
∑
i
=
1
n
p
(
x
i
)
2
\begin{aligned} \operatorname{Gini}(D) &=\sum_{i=1}^{n} p\left(x_{i}\right) *\left(1-p\left(x_{i}\right)\right) &=1-\sum_{i=1}^{n} p\left(x_{i}\right)^{2} \end{aligned}
Gini(D)=i=1∑np(xi)∗(1−p(xi))=1−i=1∑np(xi)2
df2['ratio_2'] = df2['ratio'] * df2['ratio']
df2 = df2.groupby(['证券代码', '日期'])['ratio_2'].sum().reset_index()
df2.head()
证券代码 | 日期 | ratio_2 | |
---|---|---|---|
0 | 1 | 2008/12/31 | 0.148788 |
1 | 1 | 2009/12/31 | 0.230552 |
2 | 1 | 2010/12/31 | 0.139320 |
3 | 1 | 2011/12/31 | 0.057870 |
4 | 1 | 2012/12/31 | 0.086869 |
df2['gini'] = 1 - df2['ratio_2']
df2['证券代码'] = df2['证券代码'].apply(lambda x: '#' + (6-len(str(x)))*'0' + str(x))
df2['日期'] = df2['日期'].apply(lambda x: int(str(x)[:4]))
df2 = df2[['证券代码', '日期', 'gini']]
df2.head()
证券代码 | 日期 | gini | |
---|---|---|---|
0 | #000001 | 2008 | 0.851212 |
1 | #000001 | 2009 | 0.769448 |
2 | #000001 | 2010 | 0.860680 |
3 | #000001 | 2011 | 0.942130 |
4 | #000001 | 2012 | 0.913131 |
res = res.merge(df2, on=['证券代码', '日期'])
res.head()
证券代码 | 日期 | I | gini | |
---|---|---|---|---|
0 | #000001 | 2008 | 2.085159 | 0.851212 |
1 | #000001 | 2009 | 1.671752 | 0.769448 |
2 | #000001 | 2010 | 2.108355 | 0.860680 |
3 | #000001 | 2011 | 3.150479 | 0.942130 |
4 | #000001 | 2012 | 2.718759 | 0.913131 |
import seaborn as sns
sns.distplot(res['I'])
sns.distplot(res['gini'])
两个好像大致分布是一样的吧0.0
不过可以看出gini的结果更为平滑一些
再比较一下计算效率
df2 = pd.read_csv('../data/company_data.csv')
df2['total'] = df2.groupby(['证券代码', '日期'])['收入额'].transform('sum')
df2['ratio'] = df2['收入额'] / df2['total']
%%timeit
df2['entropy'] = df2['ratio']*np.log(df2['ratio'])
df2['I'] = -df2.groupby(['证券代码', '日期'])['entropy'].transform('sum')
151 ms ± 25.6 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
df2 = pd.read_csv('../data/company_data.csv')
df2['total'] = df2.groupby(['证券代码', '日期'])['收入额'].transform('sum')
df2['ratio'] = df2['收入额'] / df2['total']
df2.head()
%%timeit
df2['ratio_2'] = df2['ratio'] * df2['ratio']
df3 = df2.groupby(['证券代码', '日期'])['ratio_2'].sum().reset_index()
df3['gini'] = 1 - df3['ratio_2']
124 ms ± 21.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
两段代码里都包含了一个groupby的操作,因此计算不是很精确,不过还是可以看出基尼指数的计算速度相较于信息增益快了20%左右
【第二题】组队学习信息表的变换
【题目描述】请把组队学习的队伍信息表变换为如下形态,其中“是否队长”一列取1表示队长,否则为0
数据非公开,这里不提供
【我的思路】这是个宽表变长表的操作,应该是可以用wide_to_long进行操作的,但还没想好wide_to_long怎么操作。
目前我的思路是先提取出所有队长的列,然后把每个队员的列的数据按列插入到队长表中
ps.后记:提交完之后我看到了Gocara大佬的答案,明白了wide_to_long的做法,这里我在我的第一版内容之后又加了wide_to_long的做法,并比对一下两种做法的效率
teams = pd.read_excel('../data/team_info.xlsx', engine='openpyxl')
teams.head()
所在群 | 队伍名称 | 队长编号 | 队长_群昵称 | 队员1 编号 | 队员_群昵称 | 队员2 编号 | 队员_群昵称.1 | 队员3 编号 | 队员_群昵称.2 | ... | 队员6 编号 | 队员_群昵称.5 | 队员7 编号 | 队员_群昵称.6 | 队员8 编号 | 队员_群昵称.7 | 队员9 编号 | 队员_群昵称.8 | 队员10编号 | 队员_群昵称.9 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Pandas数据分析 | 你说的都对队 | 5.0 | 山枫叶纷飞 | 6.0 | 蔡 | 7.0 | 安慕希 | 8.0 | 信仰 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1 | Pandas数据分析 | 熊猫人 | 175.0 | 鱼呲呲 | 44.0 | Heaven | 37.0 | 吕青 | 50.0 | 余柳成荫 | ... | 25.0 | Never say never | 55.0 | K | 120.0 | Y. | 28.0 | X.Y.Q | 151.0 | swrong |
2 | Pandas数据分析 | 中国移不动 | 107.0 | Y's | 124.0 |
|