python 数据分析--数据处理工具Pandas(2)

前面的学习中主要了解了Pandas如何构造序列和数据框,如何读取和写入各种格式的数据,以及如何对数据进行初步描述,本文将进一步了解Pandas如何处理字符串和日期数据,数据清洗,获取数据子集,透视表,分组聚合操作等内容。

4. Pandas处理字符串和日期数据

待处理的数据表[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pIBfCT7G-1585923940264)(attachment:image.png)]

数据处理要求:

  • 更改出生日期birthday和手机号tel两个字段的数据类型。
  • 根据出生日期birthday和开始工作日期start_work两个字段新增年龄和工龄两个字段。
  • 将手机号tel的中间四位隐藏起来。
  • 根据邮箱信息新增邮箱域名字段。
  • 基于other字段取出每个人员的专业信息。
import pandas as pd

#读入数据
employee_info = pd.read_excel(r"E:/Data/3/data_test03.xlsx",header=0)
employee_info.dtypes
name                  object
gender                object
birthday              object
start_work    datetime64[ns]
income                 int64
tel                    int64
email                 object
other                 object
dtype: object
# 更改数据类型
employee_info.birthday = pd.to_datetime(employee_info.birthday, format="%Y/%m/%d")
employee_info.tel = employee_info.tel.astype('str')
employee_info.dtypes
name                  object
gender                object
birthday      datetime64[ns]
start_work    datetime64[ns]
income                 int64
tel                   object
email                 object
other                 object
dtype: object
# 新增年龄和工龄字段
# 年龄 = 当天日期的年份 - 生日那一天的年份
# 工龄 = 当天日期的年份 - 开始工作那一天的年份
employee_info['age'] = pd.datetime.today().year - employee_info.birthday.dt.year
employee_info['workage'] = pd.datetime.today().year - employee_info.start_work.dt.year

# 新增邮箱域名字段
# 字符串分割、巧用了匿名函数 lambda
# split分出来的数据有两部分[邮箱名,域名],域名的索引为1

employee_info['email_domain'] = employee_info.email.apply(func = lambda x: x.split('@')[1])  
employee_info
name gender birthday start_work income tel email other age workage email_domain
0 赵一 1989-08-10 2012-09-08 15000 13611011234 zhaoyi@qq.com {教育:本科,专业:电子商务,爱好:运动} 31 8 qq.com
1 王二 1990-10-02 2014-03-06 12500 13500012234 wanger@163.com {教育:大专,专业:汽修,爱好:} 30 6 163.com
2 张三 1987-03-12 2009-01-08 18500 13515273330 zhangsan@qq.com {教育:本科,专业:数学,爱好:打篮球} 33 11 qq.com
3 李四 1991-08-16 2014-06-04 13000 13923673388 lisi@gmail.com {教育:硕士,专业:统计学,爱好:唱歌} 29 6 gmail.com
4 刘五 1992-05-24 2014-08-10 8500 17823117890 liuwu@qq.com {教育:本科,专业:美术,爱好:} 28 6 qq.com
5 雷六 1986-12-10 2010-03-10 15000 13712345612 leiliu@126.com {教育:本科,专业:化学,爱好:钓鱼} 34 10 126.com
6 贾七 1993-04-10 2015-08-01 9000 13178734511 jiaqi@136.com {教育:硕士,专业:物理,爱好:健身} 27 5 136.com
7 吴八 1988-07-19 2014-10-12 13500 17822335317 wuba@qq.com {教育:本科,专业:政治学,爱好:读书} 32 6 qq.com
# 隐藏电话号码中间四位数
# 字符串替换,巧用了匿名函数lambda
employee_info.tel = employee_info.tel.apply(func = lambda x: x.replace(x[3:7],'****'))
employee_info
name gender birthday start_work income tel email other age workage email_domain
0 赵一 1989-08-10 2012-09-08 15000 136****1234 zhaoyi@qq.com {教育:本科,专业:电子商务,爱好:运动} 31 8 qq.com
1 王二 1990-10-02 2014-03-06 12500 135****2234 wanger@163.com {教育:大专,专业:汽修,爱好:} 30 6 163.com
2 张三 1987-03-12 2009-01-08 18500 135****3330 zhangsan@qq.com {教育:本科,专业:数学,爱好:打篮球} 33 11 qq.com
3 李四 1991-08-16 2014-06-04 13000 139****3388 lisi@gmail.com {教育:硕士,专业:统计学,爱好:唱歌} 29 6 gmail.com
4 刘五 1992-05-24 2014-08-10 8500 178****7890 liuwu@qq.com {教育:本科,专业:美术,爱好:} 28 6 qq.com
5 雷六 1986-12-10 2010-03-10 15000 137****5612 leiliu@126.com {教育:本科,专业:化学,爱好:钓鱼} 34 10 126.com
6 贾七 1993-04-10 2015-08-01 9000 131****4511 jiaqi@136.com {教育:硕士,专业:物理,爱好:健身} 27 5 136.com
7 吴八 1988-07-19 2014-10-12 13500 178****5317 wuba@qq.com {教育:本科,专业:政治学,爱好:读书} 32 6 qq.com
# 根据other 字段提取每个人的专业信息
# 用正则表达式匹配专业字段,主要在匹配时 : 和 , 均为中文输入法,英文无法匹配的
employee_info['profession'] = employee_info.other.str.findall('专业:(.*?),')

# findall 提取出来的数据带[] 去除[]
employee_info.profession = employee_info.profession.astype('str')
employee_info.profession = employee_info.profession.apply(func = lambda x: x.replace(x[:],x[1:-1]))
employee_info.profession = employee_info.profession.apply(func = lambda x: x.replace(x[0],' '))
employee_info.head()
name gender birthday start_work income tel email other age workage email_domain profession
0 赵一 1989-08-10 2012-09-08 15000 136****1234 zhaoyi@qq.com {教育:本科,专业:电子商务,爱好:运动} 31 8 qq.com 电子商务
1 王二 1990-10-02 2014-03-06 12500 135****2234 wanger@163.com {教育:大专,专业:汽修,爱好:} 30 6 163.com 汽修
2 张三 1987-03-12 2009-01-08 18500 135****3330 zhangsan@qq.com {教育:本科,专业:数学,爱好:打篮球} 33 11 qq.com 数学
3 李四 1991-08-16 2014-06-04 13000 139****3388 lisi@gmail.com {教育:硕士,专业:统计学,爱好:唱歌} 29 6 gmail.com 统计学
4 刘五 1992-05-24 2014-08-10 8500 178****7890 liuwu@qq.com {教育:本科,专业:美术,爱好:} 28 6 qq.com 美术
# 剔除birthday,start_work和other变量
# 需要将axis参数设置为1,因为默认drop方法是用来删除数据框中的行记录。
employee_info.drop(['birthday','start_work','other'], axis=1,inplace=True)
employee_info
name gender income tel email age workage email_domain profession
0 赵一 15000 136****1234 zhaoyi@qq.com 31 8 qq.com 电子商务
1 王二 12500 135****2234 wanger@163.com 30 6 163.com 汽修
2 张三 18500 135****3330 zhangsan@qq.com 33 11 qq.com 数学
3 李四 13000 139****3388 lisi@gmail.com 29 6 gmail.com 统计学
4 刘五 8500 178****7890 liuwu@qq.com 28 6 qq.com 美术
5 雷六 15000 137****5612 leiliu@126.com 34 10 126.com 化学
6 贾七 9000 131****4511 jiaqi@136.com 27 5 136.com 物理
7 吴八 13500 178****5317 wuba@qq.com 32 6 qq.com 政治学

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-viK3mTil-1585923940265)(attachment:image.png)]

5. Pandas 数据清洗

在数据处理过程中,一般都需要进行数据的清洗工作,数据清洗过程主要负责看数据集是否存在重复、是否存在缺失、数据是否具有完整性和一致性、数据中是否存在异常值等。这些问题都不利于数据分析,需要加以处理。

5.1 重复观测处理

在搜集数据过程中,可能会存在重复观测的出现,例如通过网络爬虫,就比较容易产生重复数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-woWeaqTU-1585923940266)(attachment:image.png)]
上面的数据就是通过爬虫获得某APP市场中电商类APP的下载量数据(部分),通过肉眼,是能够发现这10行数据中的重复项的,例如,唯品会出现了两次、当当出现了三次。如果搜集上来的数据不是10行,而是10万行,甚至更多时,就无法通过肉眼的方式检测数据是否存在重复项了。

import pandas as pd 

dsapp = pd.read_excel(r"E:/Data/3/data_test04.xlsx")

# 重复观测检测
#使用duplicated方法进行验证,但是该方法返回的是数据集每一行的检验结果,即10行数据会返回10个bool值。
# 加any进行判断,只要有一个检测到,就代表有

print("是否存在重复观测:\n",any(dsapp.duplicated()))
是否存在重复观测:
 True
#删除重复数据

dsapp.drop_duplicates(inplace=True)
dsapp
appcategory appname comments install love size update
0 网上购物-商城-团购-优惠-快递 每日优鲜 1297 204.7万 89.00% 15.16MB 2017年10月11日
1 网上购物-商城 苏宁易购 577 7996.8万 73.00% 58.9MB 2017年09月21日
2 网上购物-商城-优惠 唯品会 2543 7090.1万 86.00% 41.43MB 2017年10月13日
4 网上购物-商城 拼多多 1921 3841.9万 95.00% 13.35MB 2017年10月11日
5 网上购物-商城-优惠 寺库奢侈品 1964 175.4万 100.00% 17.21MB 2017年09月30日
6 网上购物-商城 淘宝 14244 4.6亿 68.00% 73.78MB 2017年10月13日
7 网上购物-商城-团购-优惠 当当 134 1615.3万 61.00% 37.01MB 2017年10月17日

5.2 缺失值处理

缺失值是指数据集中的某些观测存在遗漏的指标值,缺失值的存在同样会影响到数据分析和挖掘的结果。导致观测的缺失可能有两方面原因,一方面是人为原因(如记录过程中的遗漏、个人隐私而不愿透露等),另一方面是机器或设备的故障所导致(如断电或设备老化等原因)。一般而言,当遇到缺失值(Python中用NaN表示)时,可以采用三种方法处置,分别是删除法、替换法和插补法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MysIVi7X-1585923940266)(attachment:image.png)]
上面的数据来自于某游戏公司的用户注册信息(仅以10行记录为例,该数据集中存在4条红色标注的缺失观测。

import pandas as pd 

Game_user = pd.read_excel(r"E:\Data\3\data_test05.xlsx")
print("数据集是否存在缺失值:\n",any(Game_user.isnull()))

数据集是否存在缺失值:
 True

5.2.1 删除法

# 1. 删除法处理
# 在副本上删除所有缺失的行,Game_user 不变
Game_user.dropna()    
uid regit_date gender age income
0 81200457 2016-10-30 M 23.0 6500.0
1 81201135 2016-11-08 M 27.0 10300.0
3 84639281 2017-04-17 M 26.0 6000.0
6 63881943 2015-10-07 M 21.0 10000.0
8 77638351 2016-07-12 M 25.0 18000.0
# 删除缺失值最多的那一列变量
Game_user.drop('age',axis=1) 
uid regit_date gender income
0 81200457 2016-10-30 M 6500.0
1 81201135 2016-11-08 M 10300.0
2 80043782 2016-10-13 F 13500.0
3 84639281 2017-04-17 M 6000.0
4 73499801 2016-03-21 NaN 4500.0
5 72399510 2016-01-18 M NaN
6 63881943 2015-10-07 M 10000.0
7 35442690 2015-04-10 F 5800.0
8 77638351 2016-07-12 M 18000.0
9 85200189 2017-05-18 M NaN

5.2.2 替换法

# 2. 替换法
# 缺失值用前一行值填充
Game_user.fillna(method='ffill')
uid regit_date gender age income
0 81200457 2016-10-30 M 23.0 6500.0
1 81201135 2016-11-08 M 27.0 10300.0
2 80043782 2016-10-13 F 27.0 13500.0
3 84639281 2017-04-17 M 26.0 6000.0
4 73499801 2016-03-21 M 26.0 4500.0
5 72399510 2016-01-18 M 19.0 4500.0
6 63881943 2015-10-07 M 21.0 10000.0
7 35442690 2015-04-10 F 21.0 5800.0
8 77638351 2016-07-12 M 25.0 18000.0
9 85200189 2017-05-18 M 22.0 18000.0
# 缺失值用后一行填充
Game_user.fillna(method='bfill')
uid regit_date gender age income
0 81200457 2016-10-30 M 23.0 6500.0
1 81201135 2016-11-08 M 27.0 10300.0
2 80043782 2016-10-13 F 26.0 13500.0
3 84639281 2017-04-17 M 26.0 6000.0
4 73499801 2016-03-21 M 19.0 4500.0
5 72399510 2016-01-18 M 19.0 10000.0
6 63881943 2015-10-07 M 21.0 10000.0
7 35442690 2015-04-10 F 25.0 5800.0
8 77638351 2016-07-12 M 25.0 18000.0
9 85200189 2017-05-18 M 22.0 NaN

method参数可以接受'ffill'和'bfill'两种值,分别代表前向填充和后向填充。前向填充是指用缺失值的前一个值替换(如左表所示),而后向填充则表示用缺失值的后一个值替换(如右表所示)。右表中的最后一个记录仍包含缺失值,是因为后向填充法找不到该缺失值的后一个值用于替换。缺失值的前向填充或后向填充一般适用于时间序列型的数据集,因为这样的数据前后具有连贯性,而一般的独立性样本并不适用该方法。

# 常数替换
Game_user.fillna(value = 0)
uid regit_date gender age income
0 81200457 2016-10-30 M 23.0 6500.0
1 81201135 2016-11-08 M 27.0 10300.0
2 80043782 2016-10-13 F 0.0 13500.0
3 84639281 2017-04-17 M 26.0 6000.0
4 73499801 2016-03-21 0 0.0 4500.0
5 72399510 2016-01-18 M 19.0 0.0
6 63881943 2015-10-07 M 21.0 10000.0
7 35442690 2015-04-10 F 0.0 5800.0
8 77638351 2016-07-12 M 25.0 18000.0
9 85200189 2017-05-18 M 22.0 0.0
# 统计值替换
Game_user.fillna(value= {'gender':Game_user.gender.mode()[0],
                         'age':Game_user.age.median(),
                        'income':Game_user.income.mean()})
uid regit_date gender age income
0 81200457 2016-10-30 M 23.0 6500.0
1 81201135 2016-11-08 M 27.0 10300.0
2 80043782 2016-10-13 F 23.0 13500.0
3 84639281 2017-04-17 M 26.0 6000.0
4 73499801 2016-03-21 M 23.0 4500.0
5 72399510 2016-01-18 M 19.0 9325.0
6 63881943 2015-10-07 M 21.0 10000.0
7 35442690 2015-04-10 F 23.0 5800.0
8 77638351 2016-07-12 M 25.0 18000.0
9 85200189 2017-05-18 M 22.0 9325.0

如上代码并没有实际改变df数据框的结果,因为dropna、drop和fillna方法并没有使inplace参数设置为True。可以在实际的学习和工作中挑选一个适当的缺失值处理方法,然后将该方法中的inplace参数设置为True,进而可以真正地改变你所处理的数据集。

5.3 异常值处理

异常值是指那些远离正常值的观测,即“不合群”观测。导致异常值的出现一般是人为的记录错误或者是设备的故障等,异常值的出现会对模型的创建和预测产生严重的后果。当然异常值也不一定都是坏事,有些情况下,通过寻找异常值就能够给业务带来良好的发展,如销毁“钓鱼”网站、关闭“薅羊毛”用户的权限等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cg7IWD46-1585923940268)(attachment:image.png)]

这两种方法的选择标准如下,如果数据近似服从正态分布时,优先选择n个标准差法,因为数据的分布相对比较对称;否则优先选择箱线图法,因为分位数并不会受到极端值的影响。当数据存在异常时,一般可以使用删除法将异常值删除(前提是异常观测的比例不能太大)、替换法(可以考虑使用低于判别上限的最大值或高于判别下限的最小值替换、使用均值或中位数替换等)。下面将以年为单位的太阳黑子个数为例(时间范围:1700—1988),识别并处理异常值:

# 1. 看两种方法是否都存在异常值

import pandas as pd

#数据读入
sunspots = pd.read_table(r"E:\Data\3\sunspots.csv", sep = ',')

# 标准差法检测异常值
xmean = sunspots.counts.mean()
xstd = sunspots.counts.std()
print("标准差法异常上限检测:\n",any(sunspots > xmean + 2 * xstd))
print("标准差法异常下限检测:\n",any(sunspots < xmean - 2 * xstd))

# 箱线法检测异常值
Q1 = sunspots.counts.quantile(q = 0.25)
Q3 = sunspots.counts.quantile(q = 0.75)
IQR = Q3 - Q1
print("箱线法异常上限检测:\n",any(sunspots  > Q3 + 1.5 * IQR ))
print("箱线法异常下限检测:\n",any(sunspots < Q1 - 1.5 * IQR ))
标准差法异常上限检测:
 True
标准差法异常下限检测:
 True
箱线法异常上限检测:
 True
箱线法异常下限检测:
 True

不管是标准差检验法还是箱线图检验法,都发现太阳黑子数据中存在异常值,而且异常值都是超过上限临界值的。接下来,通过绘制太阳黑子数量的直方图和核密度曲线图,用于检验数据是否近似服从正态分布,进而选择一个最终的异常值判别方法:

# 2. 绘制直方图和核密度曲线图,确定采用何种异常判别法。
import matplotlib.pyplot as plt

plt.style.use('ggplot')
sunspots.counts.plot(kind = 'hist', bins =30, normed = True)
sunspots.counts.plot(kind = 'kde')
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9VooviMK-1585923940268)(output_37_0.png)]
很明显,不管是直方图还是核密度曲线,都不服从正太分布,所以采用箱线法进行异常检测,此处也选用替换法来处理异常值。

# 替换法处理异常值:
print("异常值替换前的数据统计特征:\n", sunspots.counts.describe())
print("\n")

#箱线图中的异常值判别上限
UL = Q3 + 1.5 * IQR
print("判别异常值的上限临界值:\n", UL)

# 从数据中找出低于判别上限的最大值
replace_value = sunspots.counts[sunspots.counts < UL].max()
sunspots.counts[sunspots.counts > UL] = replace_value
print("\n")
print("异常值替换后的数据统计特征:\n", sunspots.counts.describe())
异常值替换前的数据统计特征:
 count    289.000000
mean      48.613495
std       39.474103
min        0.000000
25%       15.600000
50%       39.000000
75%       68.900000
max      190.200000
Name: counts, dtype: float64


判别异常值的上限临界值:
 148.85000000000002


异常值替换后的数据统计特征:
 count    289.000000
mean      48.066090
std       37.918895
min        0.000000
25%       15.600000
50%       39.000000
75%       68.900000
max      141.700000
Name: counts, dtype: float64


D:\Anaconda\lib\site-packages\ipykernel_launcher.py:11: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  # This is added back by InteractiveShellApp.init_path()

6. 获取数据子集

有时数据读入后并不是对整体数据进行分析,而是数据中的部分子集,例如,对于地铁乘客量可能只关心某些时间段的流量、对于商品的交易可能只需要分析某些颜色的价格变动、对于医疗诊断数据可能只对某个年龄段的人群感兴趣等。

在Pandas模块中实现数据框子集的获取可以使用iloc、loc和ix三种“方法”,这三种方法既可以对数据行进行筛选,也可以实现变量的挑选它们的语法可以表示成[rows_select,cols_select]。

df1 = pd.DataFrame({'name':['张三','李四','王二','丁一','李五'],
                    'gender':['男','女','女','女','男'],
                    'age':[23,26,22,25,27]},
                    columns = ['name','gender','age'])
df1
name gender age
0 张三 23
1 李四 26
2 王二 22
3 丁一 25
4 李五 27
# 取出所有女性的姓名和年龄
df1.iloc[1:4,[0,2]]
name age
1 李四 26
2 王二 22
3 丁一 25
df1.loc[1:3,['name','age']]
name age
1 李四 26
2 王二 22
3 丁一 25
df1.ix[1:3,[0,2]]
name age
1 李四 26
2 王二 22
3 丁一 25
# 假如数据没有行号
df2 = df1.set_index('name')
df2

df2.iloc[1:4,:]
df2.loc[['李四','王二','丁一'],:]

gender age
name
李四 26
王二 22
丁一 25
# df2.ix[1:4,:]

在上面的df1数据集中,如何返回所有男性的姓名和年龄,如果是基于条件的记录筛选,只能使用loc和ix两种方法。正如代码所示,对iloc方法的那行代码做注释,是因为iloc不允许使用条件筛选。

# 使用筛选条件,取出所有男性的姓名和年龄
# df1.iloc[df1.gender == '男',]
df1.loc[df1.gender == '男',['name','age']]
df1.ix[df1.gender == '男',['name','age']]
name age
0 张三 23
4 李五 27

7. 透视表、合并与连接、分组聚合

7.1 透视表

Pandas模块提供了实现透视表功能的pivot_table函数,*该功能的主要目的就是实现数据的汇总统计。例如,按照某个分组变量统计商品的平均价格、销售数量、最大利润等,或者按照某两个分组变量构成统计学中的列联表(计数统计),甚至是基于多个分组变量统计各组合下的均值、中位数、总和等。

  • data:指定需要构造透视表的数据集。
  • values:指定需要拉入“数值”框的字段列表。
  • index:指定需要拉入“行标签”框的字段列表。
  • columns:指定需要拉入“列标签”框的字段列表。
  • aggfunc:指定数值的统计函数,默认为统计均值,也可以指定numpy模块中的其他统计函数。
  • fill_value:指定一个标量,用于填充缺失值。
  • margins:bool类型参数,是否需要显示行或列的总计值,默认为False。
  • dropna:bool类型参数,是否需要删除整列为缺失的字段,默认为True。
  • margins_name:指定行或列的总计名称,默认为All。
# 数据读取
diamonds = pd.read_table(r"E:/Data/3/diamonds.csv", sep = ',')

# 单个分组变量的均值统计
pd.pivot_table(data= diamonds, index = 'color',values = 'price', 
               margins=True, margins_name='总计')
price
color
D 3169.954096
E 3076.752475
F 3724.886397
G 3999.135671
H 4486.669196
I 5091.874954
J 5323.818020
总计 3932.799722
import numpy as np
pd.pivot_table(data=diamonds, index='clarity', columns='cut', values = 'carat', 
               aggfunc= np.size, margins=True, margins_name='总计')
cut Fair Good Ideal Premium Very Good 总计
clarity
I1 210.0 96.0 146.0 205.0 84.0 741.0
IF 9.0 71.0 1212.0 230.0 268.0 1790.0
SI1 408.0 1560.0 4282.0 3575.0 3240.0 13065.0
SI2 466.0 1081.0 2598.0 2949.0 2100.0 9194.0
VS1 170.0 648.0 3589.0 1989.0 1775.0 8171.0
VS2 261.0 978.0 5071.0 3357.0 2591.0 12258.0
VVS1 17.0 186.0 2047.0 616.0 789.0 3655.0
VVS2 69.0 286.0 2606.0 870.0 1235.0 5066.0
总计 1610.0 4906.0 21551.0 13791.0 12082.0 53940.0

7.2 多表合并

Pandas模块同样提供了关于多表之间的合并操作concat函数:

  • objs:指定需要合并的对象,可以是序列、数据框或面板数据构成的列表。
  • axis:指定数据合并的轴,默认为0,表示合并多个数据的行,如果为1,就表示合并多个数据的列。
  • join:指定合并的方式,默认为outer,表示合并所有数据,如果改为inner,表示合并公共部分的数据。
  • join_axes:合并数据后,指定保留的数据轴。
  • ignore_index:bool类型的参数,表示是否忽略原数据集的索引,默认为False,如果设True,就表示忽略原索引并生成新索引。
  • keys:为合并后的数据添加新索引,用于区分各个数据部分。
df1 = pd.DataFrame({'name':['张三','李四','王二'],
                    'gender':['男','女','女'],
                    'age':[23,26,22,]} )
df2 = pd.DataFrame({'name':['丁一','李五'],
                    'gender':['女','男'],
                    'age':[25,27]} )
pd.concat([df1,df2],keys = ['df1','df2'], axis=0)
age gender name
df1 0 23 张三
1 26 李四
2 22 王二
df2 0 25 丁一
1 27 李五
df2 = pd.DataFrame({'Name':['丁一','李五'],
                    'gender':['女','男'],
                    'age':[25,27]} )
pd.concat([df1,df2],keys = ['df1','df2'])
Name age gender name
df1 0 NaN 23 张三
1 NaN 26 李四
2 NaN 22 王二
df2 0 丁一 25 NaN
1 李五 27 NaN

7.3 多表连接

Pandas模块同样提供了关于多表之间的连接操作merge函数,函数的最大缺点是,每次只能操作两张数据表的连接,如果有n张表需要连接,则必须经过n-1次的merge函数使用。

  • left:指定需要连接的主表。
  • right:指定需要连接的辅表。
  • how:指定连接方式,默认为inner内连,还有其他选项,如左连left、右连right和外连
  • outer。on:指定连接两张表的共同字段。
  • left_on:指定主表中需要连接的共同字段。
  • right_on:指定辅表中需要连接的共同字段。
  • left_index:bool类型参数,是否将主表中的行索引用作表连接的共同字段,默认为False。
  • right_index:bool类型参数,是否将辅表中的行索引用作表连接的共同字段,默认为False。
  • sort:bool类型参数,是否对连接后的数据按照共同字段排序,默认为False。
  • suffixes:如果数据连接的结果中存在重叠的变量名,则使用各自的前缀进行区分。
# 构造数据集
df3 = pd.DataFrame({'id':[1,2,3,4,5],'name':['张三','李四','王二','丁一','赵五'],'age':[27,24,25,23,25],'gender':['男','男','男','女','女']})
df4 = pd.DataFrame({'Id':[1,2,2,4,4,4,5],'kemu':['科目1','科目1','科目2','科目1','科目2','科目3','科目1'],'score':[83,81,87,75,86,74,88]})
df5 = pd.DataFrame({'id':[1,3,5],'name':['张三','王二','赵五'],'income':[13500,18000,15000]})

# 三表的数据连接
# 首先df3和df4连接
merge1 = pd.merge(left = df3, right = df4, how = 'left', left_on='id', right_on='Id')
merge1

# 再将连接结果与df5连接
merge2 = pd.merge(left = merge1, right = df5, how = 'left')
merge2
age gender id name Id kemu score income
0 27 1 张三 1.0 科目1 83.0 13500.0
1 24 2 李四 2.0 科目1 81.0 NaN
2 24 2 李四 2.0 科目2 87.0 NaN
3 25 3 王二 NaN NaN NaN 18000.0
4 23 4 丁一 4.0 科目1 75.0 NaN
5 23 4 丁一 4.0 科目2 86.0 NaN
6 23 4 丁一 4.0 科目3 74.0 NaN
7 25 5 赵五 5.0 科目1 88.0 15000.0

7.4 分组聚合

# 通过groupby方法,指定分组变量
grouped = diamonds.groupby(by = ['color','cut'])

# 对分组变量进行统计汇总
result = grouped.aggregate({'color':np.size, 'carat':np.min, 'price':np.mean})

# 调整变量名的顺序
result = pd.DataFrame(result, columns=['color','carat','price'])

# 数据集重命名
result.rename(columns={'color':'counts','carat':'min_weight','price':'avg_price'}, inplace=True)

# 将行索引变量数据框的变量
# result.reset_index(inplace=True)
result
counts min_weight avg_price
color cut
D Fair 163 0.25 4291.061350
Good 662 0.23 3405.382175
Ideal 2834 0.20 2629.094566
Premium 1603 0.20 3631.292576
Very Good 1513 0.23 3470.467284
E Fair 224 0.22 3682.312500
Good 933 0.23 3423.644159
Ideal 3903 0.20 2597.550090
Premium 2337 0.20 3538.914420
Very Good 2400 0.20 3214.652083
F Fair 312 0.25 3827.003205
Good 909 0.23 3495.750275
Ideal 3826 0.23 3374.939362
Premium 2331 0.20 4324.890176
Very Good 2164 0.23 3778.820240
G Fair 314 0.23 4239.254777
Good 871 0.23 4123.482204
Ideal 4884 0.23 3720.706388
Premium 2924 0.23 4500.742134
Very Good 2299 0.23 3872.753806
H Fair 303 0.33 5135.683168
Good 702 0.25 4276.254986
Ideal 3115 0.23 3889.334831
Premium 2360 0.23 5216.706780
Very Good 1824 0.23 4535.390351
I Fair 175 0.41 4685.445714
Good 522 0.30 5078.532567
Ideal 2093 0.23 4451.970377
Premium 1428 0.23 5946.180672
Very Good 1204 0.24 5255.879568
J Fair 119 0.30 4975.655462
Good 307 0.28 4574.172638
Ideal 896 0.23 4918.186384
Premium 808 0.30 6294.591584
Very Good 678 0.24 5103.513274
posted @ 2020-04-09 11:36  sinlearn  阅读(553)  评论(0编辑  收藏  举报