其他
pandas分类数据处理大全
本次来介绍关于分类数据处理的常用方法。
category
是pandas
的一种分类的定类数据类型。和文本数据.str.<methond>
一样,它也有访问器功能.cat.<method>
。
什么是分类数据? 分类数据 cat
的处理方法为什么要使用分类数据? 分类数据 cat
使用时的一些坑
什么是分类数据?
pandas
中用category
来表示分类数据。s
------
0 a
1 b
2 c
dtype: category
Categories (3, object): ['a', 'b', 'c']
cut
进行分箱操作返回的分箱就是分类类型。-----------------
0 (0.992, 3.667]
1 (0.992, 3.667]
2 (3.667, 6.333]
3 (6.333, 9.0]
4 (6.333, 9.0]
dtype: category
Categories (3, interval[float64]): [(0.992, 3.667] < (3.667, 6.333] < (6.333, 9.0]]
astype
方法转换即可,如:s
------
0 a
1 b
2 c
dtype: object
s.astype('category')
------
0 a
1 b
2 c
dtype: category
Categories (3, object): ['a', 'b', 'c']
CategoricalDtype
自定义分类数据,自定义的类型适用于以上全部方法。abc
3个分类,并指定了顺序。然后就可以通过dtype
指定自定义的数据类型了,d
不在定义类型abc
中,显示为空。# 自定义分类数据,有序
c= CategoricalDtype(categories=['a','b','c'],ordered=True)
pd.Series(list('abcabd'),dtype=c)
--------
0 a
1 b
2 c
3 a
4 b
5 NaN
dtype: category
Categories (3, object): ['a' < 'b' < 'c']
分类数据的处理方法
修改分类
.cat.rename_categories()
修改分类的名称。# 指定分类为x、y、z
s.cat.categories = ['x','y','z']
0 x
1 y
2 z
dtype: category
Categories (3, object): ['x', 'y', 'z']
s.cat.rename_categories(['m','n','o'])
# 字典形式:
s.cat.rename_categories({'x':'m','y':'n','z':'o'})
0 m
1 n
2 o
dtype: category
Categories (3, object): ['m', 'n', 'o']
追加新分类
.cat.add_categories()
追加分类。0 x
1 y
2 z
dtype: category
Categories (5, object): ['x', 'y', 'z', 'r', 't']
删除分类
remove_categories
和remove_unused_categories
。s.cat.remove_categories(['r','t'])
# 自动删除未使用的分类
s.cat.remove_unused_categories()
顺序
CategoricalDtype
设置顺序,或者通过.cat.as_ordered
设置。s.cat.as_ordered()
0 x
1 y
2 z
dtype: category
Categories (3, object): ['x' < 'y' < 'z']
# 无序设置
s.cat.as_unordered()
# 重新排序
s.cat.reorder_categories(['y','x','z'], ordered=True)
为什么使用category数据类型?
category
有以下一些好处:内存使用情况:对于重复值很多的字符串列, category
可以大大减少将数据存储在内存中所需的内存量;运行性能:进行了一些优化,可以提高某些操作的执行速度 算法库的适用:在某些情况下,一些算法模型需要 category
这种类型。比如,我们知道lightgbm
相对于xgboost
优化的一个点就是可以处理分类变量,而在构建模型时我们需要指定哪些列是分类变量,并将它们调整为category
作为超参数传给模型。
df1 = pd.DataFrame(
{
"float_1": np.random.rand(df_size),
"species": np.random.choice(["cat", "dog", "ape", "gorilla"], size=df_size),
}
)
df1_cat = df1.astype({"species": "category"})
DataFrame
,其中df1包含了species并且为object
类型,df1_cat复制了df1,但指定了species为category
类型。Index 128
float_1 800000
species 6100448
dtype: int64
Index 128
float_1 800000
species 100416
dtype: int64
category
类别后的内存使用情况。有了相当大的改进,使用的内存减少了大约60倍。没有对比,就没有伤害。category
的其中一个好处。使用category
的一些坑!
category
有很多坑要注意,这里东哥总结出以下几点,供大家参考。1、category列的操作
category
的时候需要格外小心,因为如果姿势不对,它就很可能变回object
。而变回object
的结果就是,会降低代码的性能(因为强制转换类型成本很高),并会消耗内存。category
类型的数据,我们肯定是要对其进行操作的,比如做一些转换。下面看一个例子,我们要分别对category
和object
类型进行同样的字符串大写操作,使用accessor的.str
方法。25.6 ms ± 2.07 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
1.85 ms ± 41.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
.str.upper()
仅对分类的唯一类别值调用一次,然后根据结果构造一个seires,而不是对结果中的每个值都去调用一次)。cat
和dog
两种,假设样本为10000个,4000个cat
和6000个dog
。那么如果我用对category本身处理,意味着我只分别对cat
和dog
两种类别处理一次,一共两次就解决。如果对每个值处理,那就需要样本数量10000次的处理。6100576
category
类型丢了。。结果竟是一个object
类型,数据压缩的效果也没了,现在的结果再次回到刚才的6MB内存占用。str
会直接让原本的category
类型强制转换为object
,所以内存占用又回去了,这是我为什么最开始说要格外小心。239 µs ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
2、与category列的合并
habitat
一列,并且species
中增加了sanke
。{
"species": ["cat", "dog", "ape", "gorilla", "snake"],
"habitat": ["house", "house", "jungle", "jungle", "jungle"],
}
)
df2_cat = df2.astype({"species": "category", "habitat": "category"})
category
版本,并创建了一个带有object
字符串的版本。如果将两个object
列合并在一起的,没什么意思,因为大家都知道会发生什么,object+ object= object
而已。float_1 float64
species object
habitat category
dtype: object
df1
中species
列为object
,右边的df2_cat
中species
列为category
。我们可以看到,当我们合并时,在结果中的合并列会得到category+ object= object。float_1 float64
species object
habitat category
dtype: object
category
类型必须是完全相同的。 这个与pandas
中的其他数据类型略有不同,例如所有float64
列都具有相同的数据类型,就没有什么区分。category
数据类型时,该数据类型实际上是由该特定类别中存在的一组值来描述的,因此一个类别包含["cat", "dog", "mouse"]
与类别包含["cheese", "milk", "eggs"]
是不一样的。上面的例子之所以没成功,是因为多加了一个snake
。category1+ category2=object
category1+ category1=category1
df2_cat, on="species"
).dtypes
float_1 float64
species category
habitat category
dtype: object
3、category列的分组
Dataframe
会被填成空值,还有可能直接跑死。。category
列分组时,默认情况下,即使category
类别的各个类不存在值,也会对每个类进行分组。df1_cat.astype({"species": df2_cat["species"].dtype})
.merge(df2_cat, on="species")
)
house_animals_df = habitat_df.loc[habitat_df["habitat"] == "house"]
habitat_df
,从上面例子得到的,筛选habitat
为house
的,只有dog
和cat
是house
,看下面分组结果。species
ape NaN
cat 0.501507
dog 0.501023
gorilla NaN
snake NaN
Name: float_1, dtype: float64
groupby
中得到了一堆空值。默认情况下,当按category
列分组时,即使数据不存在,pandas
也会为该类别中的每个值返回结果。略坑,如果数据类型包含很多不存在的,尤其是在多个不同的category
列上进行分组,将会极其损害性能。observed=True
到groupby
调用中,这确保了我们仅获取数据中有值的组。species
cat 0.501507
dog 0.501023
Name: float_1, dtype: float64
4、category列的索引
groupby-unstack
实现了一个交叉表,species
作为列,habitat
作为行,均为category
类型。>> species_df
species cat ape dog gorilla
habitat
house 0.501507 NaN 0.501023 NaN
jungle NaN 0.501284 NaN 0.501108
new_col
,值为1。TypeError: 'fill_value=new_col' is not present in this Categorical's categories
species
和habitat
现在均为category
类型。使用.unstack()
会把species
索引移到列索引中(类似pivot
交叉表的操作)。而当添加的新列不在species
的分类索引中时,就会报错。pandas
的category
类型非常有用,可以带来一些良好的性能优势。但是它也很娇气,使用过程中要尤为小心,确保category
类型在整个流程中保持不变,避免变回object
。本文介绍的4个点注意点:category列的变换操作:直接对category本身操作而不是对它的值操作。这样可以保留分类性质并提高性能。
category列的合并:合并时注意,要保留
category
类型,且每个dataframe
的合并列中的分类类型必须完全匹配。category列的分组:默认情况下,获得数据类型中每个值的结果,即使数据中不存在该结果。可以通过设置
observed=True
调整。category列的索引:当索引为
category
类型的时候,注意是否可能与类别变量发生奇怪的交互作用。
推荐阅读