pandas进阶系列根据datawhale远昊大佬的joyful pandas教程写一些自己的心得和补充,本文部分引用了原教程,并参考了pandas官网
另注:本文是对joyful pandas教程的延伸,完整理解需先阅读joyful pandas教程第九章
无论是interval_range
还是下一章时间序列中的date_range
都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range
中四个参数之间的恒等关系。
start + periods * freq = end
在第五章中介绍了crosstab
函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
请实现一个带有dropna
参数的my_crosstab
函数来完成上面的功能。
import numpy as np
import pandas as pd
df = pd.DataFrame({
'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
df.B = df.B.astype('category').cat.add_categories('sheep')
df.A = df.A.astype('category').cat.add_categories('d')
我利用了之前学过的pivot_table方法,先求出下表
tmp = pd.concat([df.A, df.B], axis=1)
tmp['value'] = 0
tmp
A | B | value | |
---|---|---|---|
0 | a | cat | 0 |
1 | b | cat | 0 |
2 | c | dog | 0 |
3 | a | cat | 0 |
然后利用pivot_table变形,将A的值作为索引,B的值作为列名,value经过统计后作为表中的数值,如果dropna=False
就使用concat再补充几列或几行进去,算法如下
def my_crosstab(s1, s2, dropna=True):
tmp = pd.concat([s1, s2], axis=1)
tmp['value'] = 0
res = tmp.pivot_table(index=s1.name,
columns=s2.name,
values='value',
aggfunc='count')
if not dropna:
b_cats = set(s2.cat.categories)
b_values = set(s2.unique())
df_b = pd.DataFrame(columns=list(b_cats-b_values))
res = pd.concat([res, df_b], axis=1)
a_cats = set(s1.cat.categories)
a_values = set(s1.unique())
df_a = pd.DataFrame(index=list(a_cats-a_values))
res = pd.concat([res, df_a])
res = res.rename_axis(index=s1.name, columns=s2.name).fillna(0).astype('int')
return res
展示结果:
A1 = my_crosstab(df.A, df.B, dropna=False)
A1
B | cat | dog | sheep |
---|---|---|---|
A | |||
a | 2 | 0 | 0 |
b | 1 | 0 | 0 |
c | 0 | 1 | 0 |
d | 0 | 0 | 0 |
再和教程答案的结果对比一下
def my_crosstab2(s1, s2, dropna=True):
idx1 = (s1.cat.categories if s1.dtype.name == 'category' and not dropna else s1.unique())
idx2 = (s2.cat.categories if s2.dtype.name == 'category' and not dropna else s2.unique())
res = pd.DataFrame(np.zeros((idx1.shape[0], idx2.shape[0])), index=idx1, columns=idx2)
for i, j in zip(s1, s2):
res.at[i, j] += 1
res = res.rename_axis(index=s1.name, columns=s2.name).astype('int')
return res
A2 = my_crosstab2(df.A, df.B, dropna=False)
A1.equals(A2)
True
结果一致,再对比一下两种做法的效率
%timeit my_crosstab(df.A, df.B, dropna=False)
23.3 ms ± 2.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit my_crosstab2(df.A, df.B, dropna=False)
841 µs ± 82.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
不比不知道…一比就知道差距…
答案的做法其实更容易想一些,我自己的做法可能因为最近接触Pandas接触的走火入魔了没有想着简单做法只想着变形了…
答案的做法就是先通过是否dropna来确定索引和列名,然后再按照没行的值填充列联表
可以看出变形是更费时的方法,直接确定好DataFrame的形态是效率更高的操作
现有一份关于钻石的数据集,其中carat, cut, clarity, price
分别表示克拉重量、切割质量、纯净度和价格,样例如下:
df = pd.read_csv('../data/diamonds.csv')
df.head(3)
carat | cut | clarity | price | |
---|---|---|---|---|
0 | 0.23 | Ideal | SI2 | 326 |
1 | 0.21 | Premium | SI1 | 326 |
2 | 0.23 | Good | VS1 | 327 |
df.cut
在object
类型和category
类型下使用nunique
函数,并比较它们的性能。Fair, Good, Very Good, Premium, Ideal
,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF
,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。cut, clarity
这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。Very Low, Low, Mid, High, Very High
,并把按这两种分箱方法得到的category
序列依次添加到原表中。题目1
如下对比,可以发现改变成category类型后计算速度非常快,因此要养成良好习惯对类别型数据规定好数据类型
%timeit df.cut.nunique()
3.55 ms ± 277 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
df.cut = df.cut.astype('category')
%timeit df.cut.nunique()
1.06 ms ± 517 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
题目2
在使用cat.reorder_categories
之前,先将具备条件的列定义为类别型,然后使用cat.reorder_categories
定义顺序,最后用sort_values
排序
df.clarity = df.clarity.astype('category')
df.cut = df.cut.cat.reorder_categories(['Fair', 'Good', 'Very Good', 'Premium', 'Ideal'], ordered=True)
df.clarity = df.clarity.cat.reorder_categories(['I1', 'SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'], ordered=True)
df.sort_values(['cut', 'clarity'], ascending=[False, True]).head(3)
carat | cut | clarity | price | |
---|---|---|---|---|
315 | 0.96 | Ideal | I1 | 2801 |
535 | 0.96 | Ideal | I1 | 2826 |
551 | 0.97 | Ideal | I1 | 2830 |
题目3
方法一:使用map映射
d = dict(zip(df.cut.cat.categories, range(len(df.cut.cat.categories))))
df.cut = df.cut.map(d)
df.cut.head(3)
0 4
1 3
2 1
Name: cut, dtype: category
Categories (5, int64): [0 < 1 < 2 < 3 < 4]
方法二:使用cat.codes
df.clarity = df.clarity.cat.codes
df.clarity.head(3)
0 1
1 2
2 4
Name: clarity, dtype: int8
题目4
使用cut按值分桶,qcut按频分桶即可
df['avg_price'] = df.price / df.carat
df['avg_price_qcut'] = pd.qcut(df.avg_price, 5, labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'])
df['avg_price_cut'] = pd.cut(df.avg_price, bins=[-np.infty, 1000, 3500, 5500, 18000, np.infty],
labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'])
df.head(3)
carat | cut | clarity | price | avg_price | avg_price_qcut | avg_price_cut | |
---|---|---|---|---|---|---|---|
0 | 0.23 | 4 | 1 | 326 | 1417.391304 | Very Low | Low |
1 | 0.21 | 3 | 2 | 326 | 1552.380952 | Very Low | Low |
2 | 0.23 | 1 | 4 | 327 | 1421.739130 | Very Low | Low |
题目5
可以看出,Very High和Very Low在按值分桶的时候是没出现的,故删除这两个类
df['avg_price_cut'] = df['avg_price_cut'].astype('category')
s = set(df.avg_price_cut.cat.categories)
exists = set(df.avg_price_cut.unique())
s-exists
{'Very High', 'Very Low'}
#删除,显示成功,只剩三个类了
df.avg_price_cut = df.avg_price_cut.cat.remove_categories(list(s-exists))
df.avg_price_cut.cat.categories
Index(['Low', 'Mid', 'High'], dtype='object')
题目6
思路:使用qcut
的参数retbins
获取区间,发现获取的区间是一各个节点,如下所示
df['avg_price_qcut'], interval = pd.qcut(df.avg_price, 5, labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],
retbins=True)
interval
array([ 1051.1627907 , 2295. , 3073.29326923, 4031.68316832,
5456.34266134, 17828.84615385])
因此可以利用IntervalIndex
的构造方法中的from_breaks
方法,按照每个节点之间的值来构造区间
interval = pd.IntervalIndex.from_breaks(interval, closed='both')
print(f'区间左端点: {interval.left}')
print(f'区间右端点: {interval.right}')
print(f'区间长度: {interval.length}')
区间左端点: Float64Index([1051.1627906976744, 2295.0, 3073.29326923077,
4031.6831683168316, 5456.34266133638],
dtype='float64')
区间右端点: Float64Index([ 2295.0, 3073.29326923077, 4031.6831683168316,
5456.34266133638, 17828.846153846152],
dtype='float64')
区间长度: Float64Index([1243.8372093023256, 778.29326923077, 958.3898990860616,
1424.6594930195483, 12372.503492509772],
dtype='float64')