第三章 练习题
import numpy as np
import pandas as pd
Ex1:汽车数据集
现有一份汽车数据集,其中Brand, Disp., HP
分别代表汽车品牌、发动机蓄量、发动机输出。
df = pd.read_csv('data/car.csv')
df.head()
Brand | Price | Country | Reliability | Mileage | Type | Weight | Disp. | HP | |
---|---|---|---|---|---|---|---|---|---|
0 | Eagle Summit 4 | 8895 | USA | 4.0 | 33 | Small | 2560 | 97 | 113 |
1 | Ford Escort 4 | 7402 | USA | 2.0 | 33 | Small | 2345 | 114 | 90 |
2 | Ford Festiva 4 | 6319 | Korea | 4.0 | 37 | Small | 1845 | 81 | 63 |
3 | Honda Civic 4 | 6635 | Japan/USA | 5.0 | 32 | Small | 2260 | 91 | 92 |
4 | Mazda Protege 4 | 6599 | Japan | 5.0 | 32 | Small | 2440 | 113 | 103 |
- 先过滤出所属
Country
数超过2个的汽车,即若该汽车的Country
在总体数据集中出现次数不超过2则剔除,再按Country
分组计算价格均值、价格变异系数、该Country
的汽车数量,其中变异系数的计算方法是标准差除以均值,并在结果中把变异系数重命名为CoV
。 - 按照表中位置的前三分之一、中间三分之一和后三分之一分组,统计
Price
的均值。 - 对类型
Type
分组,对Price
和HP
分别计算最大值和最小值,结果会产生多级索引,请用下划线把多级列索引合并为单层索引。 - 对类型
Type
分组,对HP
进行组内的min-max
归一化。 - 对类型
Type
分组,计算Disp.
与HP
的相关系数。
第小一问:
# 第一步,过滤出所属Country数超过2个的汽车
df1 = df.groupby('Country').filter(lambda x:x.shape[0]>2)
df1.head()
# x.shape[0]代表的是取列的值。
# 比如说shape(3,2),3代表行数,2代表列数,shape[0]代表列的值:3
Brand | Price | Country | Reliability | Mileage | Type | Weight | Disp. | HP | |
---|---|---|---|---|---|---|---|---|---|
0 | Eagle Summit 4 | 8895 | USA | 4.0 | 33 | Small | 2560 | 97 | 113 |
1 | Ford Escort 4 | 7402 | USA | 2.0 | 33 | Small | 2345 | 114 | 90 |
2 | Ford Festiva 4 | 6319 | Korea | 4.0 | 37 | Small | 1845 | 81 | 63 |
3 | Honda Civic 4 | 6635 | Japan/USA | 5.0 | 32 | Small | 2260 | 91 | 92 |
4 | Mazda Protege 4 | 6599 | Japan | 5.0 | 32 | Small | 2440 | 113 | 103 |
# 第二步,得到过滤结果再次按Country分组拿出Price列
# 用agg分别将需要处理的函数名放入 ,其中变异系数重命名用元祖的方式传入
df1.groupby('Country').Price.agg(['mean',('Cov',lambda x:x.std()/x.mean()),'count'])
mean | Cov | count | |
---|---|---|---|
Country | |||
Japan | 13938.052632 | 0.387429 | 19 |
Japan/USA | 10067.571429 | 0.240040 | 7 |
Korea | 7857.333333 | 0.243435 | 3 |
USA | 12543.269231 | 0.203344 | 26 |
第二小问:
# 第一种方法:
# 先看整体有多少行,按照三分之N的数量分配作为条件,再对条件分组。
df.shape[0]
60
condition = ['Head']*20+['Mid']*20+['Tail']*20
df.groupby(condition)['Price'].mean()
Head 9069.95
Mid 13356.40
Tail 15420.65
Name: Price, dtype: float64
# 第二种方法:
# 先构造一个长度与df样本数相同的condition序列,设置初始值为middle
# 用mask将前1/3替换为front,后1/3替换为back
condition = pd.Series(['middle']*df.shape[0]).mask(df.index<df.shape[0]/3,'front').mask(df.index>=2*df.shape[0]/3,'back')
df.groupby(condition)['Price'].mean()
back 15420.65
front 9069.95
middle 13356.40
Name: Price, dtype: float64
第三小问:
# 对df按Type分组后使用agg以传入字典的方式对指定列进行聚合
res = df.groupby('Type').agg({'Price':['max'],'HP':['min']})
# 用下划线把多级列索引合并为单层索引
res.columns = res.columns.map(lambda x:'_'.join(x))
res.head()
Price_max | HP_min | |
---|---|---|
Type | ||
Compact | 18900 | 95 |
Large | 17257 | 150 |
Medium | 24760 | 110 |
Small | 9995 | 63 |
Sporty | 13945 | 92 |
第四小问:对类型Type分组,对HP进行组内的min-max归一化。
# 先定义归一化,后面最直接调用
def normalize(s):
s_min,s_max = s.min(),s.max()
res = (s-s_min)/(s_max-s_min)
return res
df.groupby('Type')['HP'].transform(normalize).head()
# 自定义变换时需要使用transform方法,被调用的自定义函数, 其传入值为数据源的序列其传入值为数据源的序列
# 与agg的传入类型是一致的,其最后的返回结果是行列索引与数据源一致的DataFrame。
0 1.00
1 0.54
2 0.00
3 0.58
4 0.80
Name: HP, dtype: float64
第五小问:对类型Type分组,计算Disp.与HP的相关系数。
df.groupby('Type')[['Disp.','HP']].corr().head()
Disp. | HP | ||
---|---|---|---|
Type | |||
Compact | Disp. | 1.000000 | 0.586087 |
HP | 0.586087 | 1.000000 | |
Large | Disp. | 1.000000 | -0.242765 |
HP | -0.242765 | 1.000000 | |
Medium | Disp. | 1.000000 | 0.370491 |
# 参考答案
df.groupby('Type')[['HP','Disp.']].apply(lambda x:np.corrcoef(x['HP'].values,x['Disp.'].values)[0,1])
Type
Compact 0.586087
Large -0.242765
Medium 0.370491
Small 0.603916
Sporty 0.871426
Van 0.819881
dtype: float64
Ex2:实现transform函数
groupby
对象的构造方法是my_groupby(df, group_cols)
- 支持单列分组与多列分组
- 支持带有标量广播的
my_groupby(df)[col].transform(my_func)
功能 pandas
的transform
不能跨列计算,请支持此功能,即仍返回Series
但col
参数为多列
无需考虑性能与异常处理,只需实现上述功能,在给出测试样例的同时与pandas
中的transform
对比结果是否一致
第一小问:groupby对象的构造方法是my_groupby(df, group_cols)
class my_groupby:
def __init__(self, my_df, group_cols):
self.my_df = my_df.copy()
self.groups = my_df[group_cols].drop_duplicates()
if isinstance(self.groups, pd.Series):
self.groups = self.groups.to_frame()
self.group_cols = self.groups.columns.tolist()
self.groups = {i: self.groups[i].values.tolist() for i in self.groups.columns}
self.transform_col = None
def __getitem__(self, col):
self.pr_col = [col] if isinstance(col, str) else list(col)
return self
def transform(self, my_func):
self.num = len(self.groups[self.group_cols[0]])
L_order, L_value = np.array([]), np.array([])
for i in range(self.num):
group_df = self.my_df.reset_index().copy()
for col in self.group_cols:
group_df = group_df[group_df[col]==self.groups[col][i]]
group_df = group_df[self.pr_col]
if group_df.shape[1] == 1:
group_df = group_df.iloc[:, 0]
group_res = my_func(group_df)
if not isinstance(group_res, pd.Series):
group_res = pd.Series(group_res,index=group_df.index,name=group_df.name)
L_order = np.r_[L_order, group_res.index]
L_value = np.r_[L_value, group_res.values]
self.res = pd.Series(pd.Series(L_value, index=L_order).sort_index().values,index=self.my_df.reset_index().index, name=my_func.__name__)
return self.res
my_groupby(df, 'Type')
<__main__.my_groupby at 0x277b774ab38>
第二小问:
支持单列分组
def f(s):
res = (s-s.min())/(s.max()-s.min())
return res
my_groupby(df, 'Type')['Price'].transform(f).head()
0 0.733592
1 0.372003
2 0.109712
3 0.186244
4 0.177525
Name: f, dtype: float64
df.groupby('Type')['Price'].transform(f).head()
0 0.733592
1 0.372003
2 0.109712
3 0.186244
4 0.177525
Name: Price, dtype: float64
多列分组:
my_groupby(df, ['Type','Country'])['Price'].transform(f).head()
0 1.000000
1 0.000000
2 0.000000
3 0.000000
4 0.196357
Name: f, dtype: float64
df.groupby(['Type','Country'])['Price'].transform(f).head()
0 1.000000
1 0.000000
2 0.000000
3 0.000000
4 0.196357
Name: Price, dtype: float64
第三小问:支持带有标量广播的my_groupby(df)[col].transform(my_func)功能
my_groupby(df, 'Type')['Price'].transform(lambda x:x.mean()).head()
0 7682.384615
1 7682.384615
2 7682.384615
3 7682.384615
4 7682.384615
Name: <lambda>, dtype: float64
df.groupby('Type')['Price'].transform(lambda x:x.mean()).head()
0 7682.384615
1 7682.384615
2 7682.384615
3 7682.384615
4 7682.384615
Name: Price, dtype: float64
第四小问:pandas的transform不能跨列计算,请支持此功能,即仍返回Series但col参数为多列
my_groupby(df, 'Type')['Disp.', 'HP'].transform(lambda x: x['Disp.']/x.HP).head()
0 0.858407
1 1.266667
2 1.285714
3 0.989130
4 1.097087
Name: <lambda>, dtype: float64