Pandas进阶玖 类别与区间

Pandas进阶玖 类别与区间

pandas进阶系列根据datawhale远昊大佬的joyful pandas教程写一些自己的心得和补充,本文部分引用了原教程,并参考了pandas官网

另注:本文是对joyful pandas教程的延伸,完整理解需先阅读joyful pandas教程第九章

【练一练】

无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。

我的答案

start + periods * freq = end

练习

Ex1:统计未出现的类别

在第五章中介绍了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的形态是效率更高的操作

Ex2:钻石数据集

现有一份关于钻石的数据集,其中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
  1. 分别对df.cutobject类型和category类型下使用nunique函数,并比较它们的性能。
  2. 钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
  3. 分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
  4. 对每克拉的价格按照分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
  5. 第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
  6. 对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。

我的思路

题目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')

你可能感兴趣的:(pandas,datawhale,python,大数据,pandas)