Categorical Data

版本0.15中的新功能。

注意

虽然在早期版本中有pandas.Categorical,在系列DataFrame中使用分类数据的功能是新功能。

这是对pandas分类数据类型的简介,包括与R的factor的短暂比较。

分类是与统计中的分类变量相对应的熊猫数据类型:一个变量,只能包含有限的,通常固定的可能值(类别 ; R中的电平)。例如性别,社会阶层,血型,国家关系,观察时间或通过Likert量表评分。

与统计分类变量相反,分类数据可能有顺序(例如“强同意”与“同意”或“第一次观察”与“第二次观察”),但数值运算(加法,除法,...)可能。

分类数据的所有值均位于类别np.nan中。顺序由类别的顺序定义,而不是值的词汇顺序。在内部,数据结构由类别数组和代码的整数数组组成,它指向类别数组中的实际值。

分类数据类型在以下情况下很有用:

  • 一个字符串变量,只包含几个不同的值。将此类字符串变量转换为分类变量将会节省一些内存,请参见here
  • 变量的词法顺序与逻辑顺序(“一个”,“两个”,“三个”)不同。通过转换为分类并在类别上指定顺序,排序和最小/最大将使用逻辑顺序而不是词法顺序,请参见here
  • 作为一个信号给其他python库,这个列应该被当作一个分类变量(例如使用合适的统计方法或图类型)。

另请参阅API docs on categoricals

Object Creation

分类系列DataFrame中的列可以通过以下几种方式创建:

在构建系列时指定dtype="category"

In [1]: s = pd.Series(["a","b","c","a"], dtype="category")

In [2]: s
Out[2]: 
0    a
1    b
2    c
3    a
dtype: category
Categories (3, object): [a, b, c]

将现有的系列或列转换为category dtype:

In [3]: df = pd.DataFrame({"A":["a","b","c","a"]})

In [4]: df["B"] = df["A"].astype('category')

In [5]: df
Out[5]: 
   A  B
0  a  a
1  b  b
2  c  c
3  a  a

通过使用一些特殊功能:

In [6]: df = pd.DataFrame({'value': np.random.randint(0, 100, 20)})

In [7]: labels = [ "{0} - {1}".format(i, i + 9) for i in range(0, 100, 10) ]

In [8]: df['group'] = pd.cut(df.value, range(0, 105, 10), right=False, labels=labels)

In [9]: df.head(10)
Out[9]: 
   value    group
0     65  60 - 69
1     49  40 - 49
2     56  50 - 59
3     43  40 - 49
4     43  40 - 49
5     91  90 - 99
6     32  30 - 39
7     87  80 - 89
8     36  30 - 39
9      8    0 - 9

有关cut()的信息,请参阅documentation

pandas.Categorical对象传递到系列或将其分配给DataFrame

In [10]: raw_cat = pd.Categorical(["a","b","c","a"], categories=["b","c","d"],
   ....:                          ordered=False)
   ....: 

In [11]: s = pd.Series(raw_cat)

In [12]: s
Out[12]: 
0    NaN
1      b
2      c
3    NaN
dtype: category
Categories (3, object): [b, c, d]

In [13]: df = pd.DataFrame({"A":["a","b","c","a"]})

In [14]: df["B"] = raw_cat

In [15]: df
Out[15]: 
   A    B
0  a  NaN
1  b    b
2  c    c
3  a  NaN

您还可以通过将这些参数传递到astype()来指定不同排序的类别或将结果数据排序:

In [16]: s = pd.Series(["a","b","c","a"])

In [17]: s_cat = s.astype("category", categories=["b","c","d"], ordered=False)

In [18]: s_cat
Out[18]: 
0    NaN
1      b
2      c
3    NaN
dtype: category
Categories (3, object): [b, c, d]

分类数据具有特定的category dtype

In [19]: df.dtypes
Out[19]: 
A      object
B    category
dtype: object

注意

与R的因子函数相反,分类数据不会将输入值转换为字符串,类别将最终与原始值具有相同的数据类型。

注意

与R的因子函数相反,当前没有办法在创建时分配/更改标签。使用类别可在创建时间后更改类别。

要返回原始系列或numpy数组,请使用Series.astype(original_dtype)np.asarray(categorical)

In [20]: s = pd.Series(["a","b","c","a"])

In [21]: s
Out[21]: 
0    a
1    b
2    c
3    a
dtype: object

In [22]: s2 = s.astype('category')

In [23]: s2
Out[23]: 
0    a
1    b
2    c
3    a
dtype: category
Categories (3, object): [a, b, c]

In [24]: s3 = s2.astype('string')

In [25]: s3
Out[25]: 
0    a
1    b
2    c
3    a
dtype: object

In [26]: np.asarray(s2)
Out[26]: array(['a', 'b', 'c', 'a'], dtype=object)

如果您已经具有代码类别,则可以使用from_codes()构造函数在正常构造函数模式下保存factorize步骤:

In [27]: splitter = np.random.choice([0,1], 5, p=[0.5,0.5])

In [28]: s = pd.Series(pd.Categorical.from_codes(splitter, categories=["train", "test"]))

Description

对分类数据使用.describe()将产生与string类型的SeriesDataFrame类似的输出。

In [29]: cat = pd.Categorical(["a", "c", "c", np.nan], categories=["b", "a", "c"])

In [30]: df = pd.DataFrame({"cat":cat, "s":["a", "c", "c", np.nan]})

In [31]: df.describe()
Out[31]: 
       cat  s
count    3  3
unique   2  2
top      c  c
freq     2  2

In [32]: df["cat"].describe()
Out[32]: 
count     3
unique    2
top       c
freq      2
Name: cat, dtype: object

Working with categories

分类数据具有类别有序属性,其中列出了其可能的值以及顺序是否重要。这些属性显示为s.cat.categoriess.cat.ordered如果不手动指定类别和顺序,则从传递的值推断它们。

In [33]: s = pd.Series(["a","b","c","a"], dtype="category")

In [34]: s.cat.categories
Out[34]: Index([u'a', u'b', u'c'], dtype='object')

In [35]: s.cat.ordered
Out[35]: False

还可以按特定顺序传入类别:

In [36]: s = pd.Series(pd.Categorical(["a","b","c","a"], categories=["c","b","a"]))

In [37]: s.cat.categories
Out[37]: Index([u'c', u'b', u'a'], dtype='object')

In [38]: s.cat.ordered
Out[38]: False

注意

新的分类数据不会自动排序。您必须明确传递ordered=True以指示有序的Categorical

注意

Series.unique()的结果并不总是与Series.cat.categories相同,因为Series.unique()的保证,即它按出现顺序返回类别,并且它仅包括实际存在的值。

In [39]: s = pd.Series(list('babc')).astype('category', categories=list('abcd'))

In [40]: s
Out[40]: 
0    b
1    a
2    b
3    c
dtype: category
Categories (4, object): [a, b, c, d]

# categories
In [41]: s.cat.categories
Out[41]: Index([u'a', u'b', u'c', u'd'], dtype='object')

# uniques
In [42]: s.unique()
Out[42]: 
[b, a, c]
Categories (3, object): [b, a, c]

Renaming categories

通过向Series.cat.categories属性分配新值或使用Categorical.rename_categories()方法重命名类别:

In [43]: s = pd.Series(["a","b","c","a"], dtype="category")

In [44]: s
Out[44]: 
0    a
1    b
2    c
3    a
dtype: category
Categories (3, object): [a, b, c]

In [45]: s.cat.categories = ["Group %s" % g for g in s.cat.categories]

In [46]: s
Out[46]: 
0    Group a
1    Group b
2    Group c
3    Group a
dtype: category
Categories (3, object): [Group a, Group b, Group c]

In [47]: s.cat.rename_categories([1,2,3])
Out[47]: 
0    1
1    2
2    3
3    1
dtype: category
Categories (3, int64): [1, 2, 3]

注意

与R的因子相反,分类数据可以具有除字符串之外的其他类型的类别。

注意

请注意,分配新类别是一个内部操作,而大多数Series.cat下的其他操作默认返回一系列新的类型类别

类别必须是唯一的或产生ValueError

In [48]: try:
   ....:     s.cat.categories = [1,1,1]
   ....: except ValueError as e:
   ....:     print("ValueError: " + str(e))
   ....: 
ValueError: Categorical categories must be unique

Appending new categories

可以使用Categorical.add_categories()方法来追加类别:

In [49]: s = s.cat.add_categories([4])

In [50]: s.cat.categories
Out[50]: Index([u'Group a', u'Group b', u'Group c', 4], dtype='object')

In [51]: s
Out[51]: 
0    Group a
1    Group b
2    Group c
3    Group a
dtype: category
Categories (4, object): [Group a, Group b, Group c, 4]

Removing categories

可以使用Categorical.remove_categories()方法来删除类别。删除的值将替换为np.nan

In [52]: s = s.cat.remove_categories([4])

In [53]: s
Out[53]: 
0    Group a
1    Group b
2    Group c
3    Group a
dtype: category
Categories (3, object): [Group a, Group b, Group c]

Removing unused categories

删除未使用的类别也可以:

In [54]: s = pd.Series(pd.Categorical(["a","b","a"], categories=["a","b","c","d"]))

In [55]: s
Out[55]: 
0    a
1    b
2    a
dtype: category
Categories (4, object): [a, b, c, d]

In [56]: s.cat.remove_unused_categories()
Out[56]: 
0    a
1    b
2    a
dtype: category
Categories (2, object): [a, b]

Setting categories

如果您希望在一个步骤中删除并添加新类别(具有一些速度优势),或只是将类别设置为预定义的比例,请使用Categorical.set_categories()

In [57]: s = pd.Series(["one","two","four", "-"], dtype="category")

In [58]: s
Out[58]: 
0     one
1     two
2    four
3       -
dtype: category
Categories (4, object): [-, four, one, two]

In [59]: s = s.cat.set_categories(["one","two","three","four"])

In [60]: s
Out[60]: 
0     one
1     two
2    four
3     NaN
dtype: category
Categories (4, object): [one, two, three, four]

注意

请注意,Categorical.set_categories()无法知道某个类别是否被有意省略,或者因为类型差异(例如numpys S1 dtype和python字符串)而拼写错误或(在Python3下)。

Sorting and Order

警告

默认的构造在v0.16.0中从先前隐式的ordered=True改变为ordered=False

如果对类别数据排序(s.cat.ordered == True),具有含义并且某些操作是可能的。如果分类是无序的,.min()/.max()会引发一个TypeError

In [61]: s = pd.Series(pd.Categorical(["a","b","c","a"], ordered=False))

In [62]: s.sort_values(inplace=True)

In [63]: s = pd.Series(["a","b","c","a"]).astype('category', ordered=True)

In [64]: s.sort_values(inplace=True)

In [65]: s
Out[65]: 
0    a
3    a
1    b
2    c
dtype: category
Categories (3, object): [a < b < c]

In [66]: s.min(), s.max()
Out[66]: ('a', 'c')

您可以使用as_ordered()或使用as_unordered()无序排序设置要排序的分类数据。这些将默认返回对象。

In [67]: s.cat.as_ordered()
Out[67]: 
0    a
3    a
1    b
2    c
dtype: category
Categories (3, object): [a < b < c]

In [68]: s.cat.as_unordered()
Out[68]: 
0    a
3    a
1    b
2    c
dtype: category
Categories (3, object): [a, b, c]

排序将使用类别定义的顺序,而不是数据类型上存在的任何词法顺序。这对于字符串和数字数据是正确的:

In [69]: s = pd.Series([1,2,3,1], dtype="category")

In [70]: s = s.cat.set_categories([2,3,1], ordered=True)

In [71]: s
Out[71]: 
0    1
1    2
2    3
3    1
dtype: category
Categories (3, int64): [2 < 3 < 1]

In [72]: s.sort_values(inplace=True)

In [73]: s
Out[73]: 
1    2
2    3
0    1
3    1
dtype: category
Categories (3, int64): [2 < 3 < 1]

In [74]: s.min(), s.max()
Out[74]: (2, 1)

Reordering

可以通过Categorical.reorder_categories()Categorical.set_categories()方法重新排序类别。对于Categorical.reorder_categories(),所有旧类别都必须包含在新类别中,不允许使用新类别。这将必然使排序顺序与类别顺序相同。

In [75]: s = pd.Series([1,2,3,1], dtype="category")

In [76]: s = s.cat.reorder_categories([2,3,1], ordered=True)

In [77]: s
Out[77]: 
0    1
1    2
2    3
3    1
dtype: category
Categories (3, int64): [2 < 3 < 1]

In [78]: s.sort_values(inplace=True)

In [79]: s
Out[79]: 
1    2
2    3
0    1
3    1
dtype: category
Categories (3, int64): [2 < 3 < 1]

In [80]: s.min(), s.max()
Out[80]: (2, 1)

注意

请注意分配新类别和重新排序类别之间的差异:首先重命名类别,因此在系列中的各个值,但如果第一个位置最后排序,重命名的值仍将最后排序。重新排序意味着值的排序方式不同,但不是系列中的单个值发生更改。

注意

如果分类未排序,Series.min()Series.max()会引发TypeError+-*/和基于它们的操作(例如Series.median(),如果数组的长度是偶数,则需要计算两个值之间的平均值)不起作用,并产生TypeError

Multi Column Sorting

分类类型列将以与其他列类似的方式参与多列排序。分类的排序由该列的categories确定。

In [81]: dfs = pd.DataFrame({'A' : pd.Categorical(list('bbeebbaa'), categories=['e','a','b'], ordered=True),
   ....:                     'B' : [1,2,1,2,2,1,2,1] })
   ....: 

In [82]: dfs.sort_values(by=['A', 'B'])
Out[82]: 
   A  B
2  e  1
3  e  2
7  a  1
6  a  2
0  b  1
5  b  1
1  b  2
4  b  2

重新排序categories会更改未来排序。

In [83]: dfs['A'] = dfs['A'].cat.reorder_categories(['a','b','e'])

In [84]: dfs.sort_values(by=['A','B'])
Out[84]: 
   A  B
7  a  1
6  a  2
0  b  1
5  b  1
1  b  2
4  b  2
2  e  1
3  e  2

Comparisons

在三种情况下可以比较分类数据与其他对象:

  • 将等式(==!=)与类别对象(列表,系列,数组,...)进行比较,其长度与分类数据相同。
  • all comparisons (==, !=, >, >=, <, and <=) of categorical data to another categorical Series, when ordered==True and the categories are the same.
  • 分类数据与标量的所有比较。

所有其他比较,特别是具有不同类别的两个分类的“非等同”比较,或者具有任何类似列表的对象的分类,将引起TypeError。

注意

对分类数据与系列np.array列表或具有不同类别或排序的分类数据的任何“不等同”比较TypeError,因为自定义类别排序可以用两种方式解释:一种考虑到排序,一种没有。

In [85]: cat = pd.Series([1,2,3]).astype("category", categories=[3,2,1], ordered=True)

In [86]: cat_base = pd.Series([2,2,2]).astype("category", categories=[3,2,1], ordered=True)

In [87]: cat_base2 = pd.Series([2,2,2]).astype("category", ordered=True)

In [88]: cat
Out[88]: 
0    1
1    2
2    3
dtype: category
Categories (3, int64): [3 < 2 < 1]

In [89]: cat_base
Out[89]: 
0    2
1    2
2    2
dtype: category
Categories (3, int64): [3 < 2 < 1]

In [90]: cat_base2
Out[90]: 
0    2
1    2
2    2
dtype: category
Categories (1, int64): [2]

比较具有相同类别和排序或标量作品的分类:

In [91]: cat > cat_base
Out[91]: 
0     True
1    False
2    False
dtype: bool

In [92]: cat > 2
Out[92]: 
0     True
1    False
2    False
dtype: bool

平等比较与任何相同长度和标量的类似列表对象一起使用:

In [93]: cat == cat_base
Out[93]: 
0    False
1     True
2    False
dtype: bool

In [94]: cat == np.array([1,2,3])
Out[94]: 
0    True
1    True
2    True
dtype: bool

In [95]: cat == 2
Out[95]: 
0    False
1     True
2    False
dtype: bool

这不工作,因为类别不一样:

In [96]: try:
   ....:     cat > cat_base2
   ....: except TypeError as e:
   ....:      print("TypeError: " + str(e))
   ....: 
TypeError: Categoricals can only be compared if 'categories' are the same

如果要对类别序列与不是分类数据的类似列表对象执行“非等同”比较,则需要显式并将分类数据转换回原始值:

In [97]: base = np.array([1,2,3])

In [98]: try:
   ....:     cat > base
   ....: except TypeError as e:
   ....:      print("TypeError: " + str(e))
   ....: 
TypeError: Cannot compare a Categorical for op __gt__ with type <type 'numpy.ndarray'>.
If you want to compare values, use 'np.asarray(cat) <op> other'.

In [99]: np.asarray(cat) > base
Out[99]: array([False, False, False], dtype=bool)

Operations

除了Series.min()Series.max()Series.mode(),以下操作对于分类数据是可能的:

Series.value_counts()系列方法将使用所有类别,即使数据中不存在某些类别:

In [100]: s = pd.Series(pd.Categorical(["a","b","c","c"], categories=["c","a","b","d"]))

In [101]: s.value_counts()
Out[101]: 
c    2
b    1
a    1
d    0
dtype: int64

Groupby还将显示“未使用”类别:

In [102]: cats = pd.Categorical(["a","b","b","b","c","c","c"], categories=["a","b","c","d"])

In [103]: df = pd.DataFrame({"cats":cats,"values":[1,2,2,2,3,4,5]})

In [104]: df.groupby("cats").mean()
Out[104]: 
      values
cats        
a        1.0
b        2.0
c        4.0
d        NaN

In [105]: cats2 = pd.Categorical(["a","a","b","b"], categories=["a","b","c"])

In [106]: df2 = pd.DataFrame({"cats":cats2,"B":["c","d","c","d"], "values":[1,2,3,4]})

In [107]: df2.groupby(["cats","B"]).mean()
Out[107]: 
        values
cats B        
a    c     1.0
     d     2.0
b    c     3.0
     d     4.0
c    c     NaN
     d     NaN

数据透视表:

In [108]: raw_cat = pd.Categorical(["a","a","b","b"], categories=["a","b","c"])

In [109]: df = pd.DataFrame({"A":raw_cat,"B":["c","d","c","d"], "values":[1,2,3,4]})

In [110]: pd.pivot_table(df, values='values', index=['A', 'B'])
Out[110]: 
A  B
a  c    1.0
   d    2.0
b  c    3.0
   d    4.0
c  c    NaN
   d    NaN
Name: values, dtype: float64

Data munging

优化的pandas数据访问方法.loc.iloc.ix .at.iat,正常工作。唯一的区别是返回类型(用于获取),并且只能分配类别中的值。

Getting

如果切片操作返回DataFrame或类型系列的列,则会保留category dtype。

In [111]: idx = pd.Index(["h","i","j","k","l","m","n",])

In [112]: cats = pd.Series(["a","b","b","b","c","c","c"], dtype="category", index=idx)

In [113]: values= [1,2,2,2,3,4,5]

In [114]: df = pd.DataFrame({"cats":cats,"values":values}, index=idx)

In [115]: df.iloc[2:4,:]
Out[115]: 
  cats  values
j    b       2
k    b       2

In [116]: df.iloc[2:4,:].dtypes
Out[116]: 
cats      category
values       int64
dtype: object

In [117]: df.loc["h":"j","cats"]
Out[117]: 
h    a
i    b
j    b
Name: cats, dtype: category
Categories (3, object): [a, b, c]

In [118]: df.ix["h":"j",0:1]
Out[118]: 
  cats
h    a
i    b
j    b

In [119]: df[df["cats"] == "b"]
Out[119]: 
  cats  values
i    b       2
j    b       2
k    b       2

未保留类别类型的示例是,如果您只需要一行:生成的Series是dtype object

# get the complete "h" row as a Series
In [120]: df.loc["h", :]
Out[120]: 
cats      a
values    1
Name: h, dtype: object

从分类数据返回单个项目也将返回值,而不是长度为“1”的分类。

In [121]: df.iat[0,0]
Out[121]: 'a'

In [122]: df["cats"].cat.categories = ["x","y","z"]

In [123]: df.at["h","cats"] # returns a string
Out[123]: 'x'

注意

这与R的因子函数不同,其中factor(c(1,2,3))[1]返回单个值

要获取类型category类型的单个值Series,请传入具有单个值的列表:

In [124]: df.loc[["h"],"cats"]
Out[124]: 
h    x
Name: cats, dtype: category
Categories (3, object): [x, y, z]

String and datetime accessors

版本0.17.1中的新功能。

如果s.cat.categories是适当类型,则访问器.dt.str

In [125]: str_s = pd.Series(list('aabb'))

In [126]: str_cat = str_s.astype('category')

In [127]: str_cat
Out[127]: 
0    a
1    a
2    b
3    b
dtype: category
Categories (2, object): [a, b]

In [128]: str_cat.str.contains("a")
Out[128]: 
0     True
1     True
2    False
3    False
dtype: bool

In [129]: date_s = pd.Series(pd.date_range('1/1/2015', periods=5))

In [130]: date_cat = date_s.astype('category')

In [131]: date_cat
Out[131]: 
0   2015-01-01
1   2015-01-02
2   2015-01-03
3   2015-01-04
4   2015-01-05
dtype: category
Categories (5, datetime64[ns]): [2015-01-01, 2015-01-02, 2015-01-03, 2015-01-04, 2015-01-05]

In [132]: date_cat.dt.day
Out[132]: 
0    1
1    2
2    3
3    4
4    5
dtype: int64

注意

The returned Series (or DataFrame) is of the same type as if you used the .str.<method> / .dt.<method> on a Series of that type (and not of type category! )。

这意味着,从Series的访问器上的方法和属性返回的值以及此Series的访问器上的方法和属性的返回值都转换为类型之一类别将相等:

In [133]: ret_s = str_s.str.contains("a")

In [134]: ret_cat = str_cat.str.contains("a")

In [135]: ret_s.dtype == ret_cat.dtype
Out[135]: True

In [136]: ret_s == ret_cat
Out[136]: 
0    True
1    True
2    True
3    True
dtype: bool

注意

工作在categories上完成,然后构建新的Series如果你有一个Series类型字符串,其中很多元素被重复(即,Series中的唯一元素的数量比Series的长度)。在这种情况下,将原始Series转换为category之一并使用.str.<method>.dt.<property>

Setting

在分类列(或系列)中设置值可以工作,只要该值包含在类别中:

In [137]: idx = pd.Index(["h","i","j","k","l","m","n"])

In [138]: cats = pd.Categorical(["a","a","a","a","a","a","a"], categories=["a","b"])

In [139]: values = [1,1,1,1,1,1,1]

In [140]: df = pd.DataFrame({"cats":cats,"values":values}, index=idx)

In [141]: df.iloc[2:4,:] = [["b",2],["b",2]]

In [142]: df
Out[142]: 
  cats  values
h    a       1
i    a       1
j    b       2
k    b       2
l    a       1
m    a       1
n    a       1

In [143]: try:
   .....:     df.iloc[2:4,:] = [["c",3],["c",3]]
   .....: except ValueError as e:
   .....:     print("ValueError: " + str(e))
   .....: 
ValueError: Cannot setitem on a Categorical with a new category, set the categories first

通过指定分类数据设置值还将检查类别是否匹配:

In [144]: df.loc["j":"k","cats"] = pd.Categorical(["a","a"], categories=["a","b"])

In [145]: df
Out[145]: 
  cats  values
h    a       1
i    a       1
j    a       2
k    a       2
l    a       1
m    a       1
n    a       1

In [146]: try:
   .....:     df.loc["j":"k","cats"] = pd.Categorical(["b","b"], categories=["a","b","c"])
   .....: except ValueError as e:
   .....:     print("ValueError: " + str(e))
   .....: 
ValueError: Cannot set a Categorical with another, without identical categories

为其他类型的列的某些部分分配分类将使用以下值:

In [147]: df = pd.DataFrame({"a":[1,1,1,1,1], "b":["a","a","a","a","a"]})

In [148]: df.loc[1:2,"a"] = pd.Categorical(["b","b"], categories=["a","b"])

In [149]: df.loc[2:3,"b"] = pd.Categorical(["b","b"], categories=["a","b"])

In [150]: df
Out[150]: 
   a  b
0  1  a
1  b  a
2  b  b
3  1  b
4  1  a

In [151]: df.dtypes
Out[151]: 
a    object
b    object
dtype: object

Merging

您可以将包含分类数据的两个DataFrames合并在一起,但这些分类的类别需要相同:

In [152]: cat = pd.Series(["a","b"], dtype="category")

In [153]: vals = [1,2]

In [154]: df = pd.DataFrame({"cats":cat, "vals":vals})

In [155]: res = pd.concat([df,df])

In [156]: res
Out[156]: 
  cats  vals
0    a     1
1    b     2
0    a     1
1    b     2

In [157]: res.dtypes
Out[157]: 
cats    category
vals       int64
dtype: object

在这种情况下,类别不一样,因此会出现错误:

In [158]: df_different = df.copy()

In [159]: df_different["cats"].cat.categories = ["c","d"]

In [160]: try:
   .....:     pd.concat([df,df_different])
   .....: except ValueError as e:
   .....:     print("ValueError: " + str(e))
   .....: 

这同样适用于df.append(df_different)

Unioning

版本0.19.0中的新功能。

如果要组合不一定具有相同类别的类别,union_categoricals函数将组合类似列表的类别。新类别将是合并的类别的并集。

In [161]: from pandas.types.concat import union_categoricals

In [162]: a = pd.Categorical(["b", "c"])

In [163]: b = pd.Categorical(["a", "b"])

In [164]: union_categoricals([a, b])
Out[164]: 
[b, c, a, b]
Categories (3, object): [b, c, a]

默认情况下,生成的类别将按照在数据中显示的顺序排列。如果要对类别进行排序,请使用sort_categories=True参数。

In [165]: union_categoricals([a, b], sort_categories=True)
Out[165]: 
[b, c, a, b]
Categories (3, object): [a, b, c]

union_categoricals也适用于组合相同类别和顺序信息的两个分类(例如,您也可以append)的“简单”情况。

In [166]: a = pd.Categorical(["a", "b"], ordered=True)

In [167]: b = pd.Categorical(["a", "b", "a"], ordered=True)

In [168]: union_categoricals([a, b])
Out[168]: 
[a, b, a, b, a]
Categories (2, object): [a < b]

以下引发TypeError,因为类别是有序的并且不相同。

In [1]: a = pd.Categorical(["a", "b"], ordered=True)
In [2]: b = pd.Categorical(["a", "b", "c"], ordered=True)
In [3]: union_categoricals([a, b])
Out[3]:
TypeError: to union ordered Categoricals, all categories must be the same

union_categoricals也适用于包含分类数据的CategoricalIndexSeries,但请注意,生成的数组将始终是一个简单的Categorical

In [169]: a = pd.Series(["b", "c"], dtype='category')

In [170]: b = pd.Series(["a", "b"], dtype='category')

In [171]: union_categoricals([a, b])
Out[171]: 
[b, c, a, b]
Categories (3, object): [b, c, a]

注意

union_categoricals可以在合并分类时重新编码类别的整数代码。这可能是你想要的,但如果你依赖于类别的确切编号,请注意。

In [172]: c1 = pd.Categorical(["b", "c"])

In [173]: c2 = pd.Categorical(["a", "b"])

In [174]: c1
Out[174]: 
[b, c]
Categories (2, object): [b, c]

# "b" is coded to 0
In [175]: c1.codes
Out[175]: array([0, 1], dtype=int8)

In [176]: c2
Out[176]: 
[a, b]
Categories (2, object): [a, b]

# "b" is coded to 1
In [177]: c2.codes
Out[177]: array([0, 1], dtype=int8)

In [178]: c = union_categoricals([c1, c2])

In [179]: c
Out[179]: 
[b, c, a, b]
Categories (3, object): [b, c, a]

# "b" is coded to 0 throughout, same as c1, different from c2
In [180]: c.codes
Out[180]: array([0, 1, 2, 0], dtype=int8)

Concatenation

本节介绍category dtype特有的连接。有关一般说明,请参阅Concatenating objects

默认情况下,包含相同类别的SeriesDataFrame连接会导致category dtype,否则会导致object dtype 。使用.astypeunion_categoricals可获取category结果。

# same categories
In [181]: s1 = pd.Series(['a', 'b'], dtype='category')

In [182]: s2 = pd.Series(['a', 'b', 'a'], dtype='category')

In [183]: pd.concat([s1, s2])
Out[183]: 
0    a
1    b
0    a
1    b
2    a
dtype: category
Categories (2, object): [a, b]

# different categories
In [184]: s3 = pd.Series(['b', 'c'], dtype='category')

In [185]: pd.concat([s1, s3])
Out[185]: 
0    a
1    b
0    b
1    c
dtype: object

In [186]: pd.concat([s1, s3]).astype('category')
Out[186]: 
0    a
1    b
0    b
1    c
dtype: category
Categories (3, object): [a, b, c]

In [187]: union_categoricals([s1.values, s3.values])
Out[187]: 
[a, b, b, c]
Categories (3, object): [a, b, c]

下表总结了Categoricals相关并置的结果。

arg1 arg2 结果
类别 类别(相同类别) 类别
类别 类别(不同类别,均未排序) 对象(dtype被推断)
类别 类别(不同类别,任一个是有序的) 对象(dtype被推断)
类别 不是类别 对象(dtype被推断)

Getting Data In/Out

版本0.15.2中的新功能。

将数据(系列)写入到包含category dtype的HDF存储中,在0.15.2中实现。有关示例和警告,请参见here

在0.15.2中实现向Stata格式文件写入数据和从中读取数据。有关示例和警告,请参见here

写入CSV文件将转换数据,有效地删除有关分类(类别和排序)的任何信息。因此,如果您读回CSV文件,则必须将相关列转换回类别,并分配正确的类别和类别顺序。

In [188]: s = pd.Series(pd.Categorical(['a', 'b', 'b', 'a', 'a', 'd']))

# rename the categories
In [189]: s.cat.categories = ["very good", "good", "bad"]

# reorder the categories and add missing categories
In [190]: s = s.cat.set_categories(["very bad", "bad", "medium", "good", "very good"])

In [191]: df = pd.DataFrame({"cats":s, "vals":[1,2,3,4,5,6]})

In [192]: csv = StringIO()

In [193]: df.to_csv(csv)

In [194]: df2 = pd.read_csv(StringIO(csv.getvalue()))

In [195]: df2.dtypes
Out[195]: 
Unnamed: 0     int64
cats          object
vals           int64
dtype: object

In [196]: df2["cats"]
Out[196]: 
0    very good
1         good
2         good
3    very good
4    very good
5          bad
Name: cats, dtype: object

# Redo the category
In [197]: df2["cats"] = df2["cats"].astype("category")

In [198]: df2["cats"].cat.set_categories(["very bad", "bad", "medium", "good", "very good"],
   .....:                                inplace=True)
   .....: 

In [199]: df2.dtypes
Out[199]: 
Unnamed: 0       int64
cats          category
vals             int64
dtype: object

In [200]: df2["cats"]
Out[200]: 
0    very good
1         good
2         good
3    very good
4    very good
5          bad
Name: cats, dtype: category
Categories (5, object): [very bad, bad, medium, good, very good]

这同样适用于使用to_sql写入SQL数据库。

Missing Data

pandas主要使用值np.nan来表示缺失的数据。它默认不包括在计算中。请参阅Missing Data section

Missing values should not be included in the Categorical’s categories, only in the values. 相反,应当理解NaN是不同的,并且总是一种可能性。使用分类的codes时,缺少的值将始终具有-1的代码。

In [201]: s = pd.Series(["a", "b", np.nan, "a"], dtype="category")

# only two categories
In [202]: s
Out[202]: 
0      a
1      b
2    NaN
3      a
dtype: category
Categories (2, object): [a, b]

In [203]: s.cat.codes
Out[203]: 
0    0
1    1
2   -1
3    0
dtype: int8

处理缺失数据的方法,例如isnull()fillna()dropna()

In [204]: s = pd.Series(["a", "b", np.nan], dtype="category")

In [205]: s
Out[205]: 
0      a
1      b
2    NaN
dtype: category
Categories (2, object): [a, b]

In [206]: pd.isnull(s)
Out[206]: 
0    False
1    False
2     True
dtype: bool

In [207]: s.fillna("a")
Out[207]: 
0    a
1    b
2    a
dtype: category
Categories (2, object): [a, b]

Differences to R’s factor

可以观察到以下与R的因子函数的差异:

  • R的级别被命名为类别
  • R的级别始终为字符串类型,而pandas中的类别可以是任何类型。
  • 在创建时无法指定标签。之后使用s.cat.rename_categories(new_labels)
  • 与R的因子函数相反,使用分类数据作为创建新类别序列的唯一输入,不会删除未使用的类别,但创建一个新的类别序列,通过一个!
  • R允许在其级别(pandas'类别)中包含缺失值。Pandas不允许NaN类别,但是缺少的值仍然可以在中。

Gotchas

Memory Usage

Categorical的内存使用量与类别数量乘以数据长度成正比。相反,object dtype是一个常数乘以数据长度。

In [208]: s = pd.Series(['foo','bar']*1000)

# object dtype
In [209]: s.nbytes
Out[209]: 16000

# category dtype
In [210]: s.astype('category').nbytes
Out[210]: 2016

注意

如果类别数接近数据长度,则Categorical将使用与等同的object dtype表示近似相同或更多的存储器。

In [211]: s = pd.Series(['foo%04d' % i for i in range(2000)])

# object dtype
In [212]: s.nbytes
Out[212]: 16000

# category dtype
In [213]: s.astype('category').nbytes
Out[213]: 20000

Old style constructor usage

在早于pandas 0.15的版本中,可以通过传递预先计算的代码(称为标签)而不是类别值来构建分类代码被解释为指向-1作为NaN的类别的指针。这种类型的构造函数用法由特殊构造函数Categorical.from_codes()替换。

不幸的是,在一些特殊情况下,使用代码假定旧样式构造函数使用将与当前的熊猫版本,将导致一些微妙的错误:

>>> cat = pd.Categorical([1,2], [1,2,3])
>>> # old version
>>> cat.get_values()
array([2, 3], dtype=int64)
>>> # new version
>>> cat.get_values()
array([1, 2], dtype=int64)

警告

如果您对旧版本的pandas使用分类,请在升级前审核您的代码,并更改代码以使用from_codes()构造函数。

Categorical is not a numpy array

目前,分类数据和底层分类实现为python对象,而不是低级numpy数组dtype。这导致一些问题。

numpy本身不知道新的dtype

In [214]: try:
   .....:     np.dtype("category")
   .....: except TypeError as e:
   .....:     print("TypeError: " + str(e))
   .....: 
TypeError: data type "category" not understood

In [215]: dtype = pd.Categorical(["a"]).dtype

In [216]: try:
   .....:     np.dtype(dtype)
   .....: except TypeError as e:
   .....:      print("TypeError: " + str(e))
   .....: 
TypeError: data type not understood

Dtype比较工作:

In [217]: dtype == np.str_
Out[217]: False

In [218]: np.str_ == dtype
Out[218]: False

要检查系列是否包含分类数据,使用pandas 0.16或更高版本,请使用hasattr(s, 'cat')

In [219]: hasattr(pd.Series(['a'], dtype='category'), 'cat')
Out[219]: True

In [220]: hasattr(pd.Series(['a']), 'cat')
Out[220]: False

在类型category系列上使用numpy功能不应该工作,因为分类不是数字数据.categories是数字的情况)。

In [221]: s = pd.Series(pd.Categorical([1,2,3,4]))

In [222]: try:
   .....:     np.sum(s)
   .....: except TypeError as e:
   .....:      print("TypeError: " + str(e))
   .....: 
TypeError: Categorical cannot perform the operation sum

注意

如果这样的功能工作,请在https://github.com/pandas-dev/pandas提交错误!

dtype in apply

Pandas目前在应用函数中不保留dtype:如果沿行应用,您将获得object dtype系列 - >获取一个元素将返回一个基本类型),沿列应用也将转换为对象。

In [223]: df = pd.DataFrame({"a":[1,2,3,4],
   .....:                    "b":["a","b","c","d"],
   .....:                    "cats":pd.Categorical([1,2,3,2])})
   .....: 

In [224]: df.apply(lambda row: type(row["cats"]), axis=1)
Out[224]: 
0    <type 'int'>
1    <type 'int'>
2    <type 'int'>
3    <type 'int'>
dtype: object

In [225]: df.apply(lambda col: col.dtype, axis=0)
Out[225]: 
a       object
b       object
cats    object
dtype: object

Categorical Index

版本0.16.1中的新功能。

在版本0.16.1中引入了新的CategoricalIndex索引类型。有关详细说明,请参阅advanced indexing docs

设置索引,将创建CategoricalIndex

In [226]: cats = pd.Categorical([1,2,3,4], categories=[4,2,3,1])

In [227]: strings = ["a","b","c","d"]

In [228]: values = [4,2,3,1]

In [229]: df = pd.DataFrame({"strings":strings, "values":values}, index=cats)

In [230]: df.index
Out[230]: CategoricalIndex([1, 2, 3, 4], categories=[4, 2, 3, 1], ordered=False, dtype='category')

# This now sorts by the categories order
In [231]: df.sort_index()
Out[231]: 
  strings  values
4       d       1
2       b       2
3       c       3
1       a       4

In previous versions (<0.16.1) there is no index of type category, so setting the index to categorical column will convert the categorical data to a “normal” dtype first and therefore remove any custom ordering of the categories.

Side Effects

分类构造系列将不会复制输入分类这意味着更改系列将在大多数情况下更改原始分类

In [232]: cat = pd.Categorical([1,2,3,10], categories=[1,2,3,4,10])

In [233]: s = pd.Series(cat, name="cat")

In [234]: cat
Out[234]: 
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]

In [235]: s.iloc[0:2] = 10

In [236]: cat
Out[236]: 
[10, 10, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]

In [237]: df = pd.DataFrame(s)

In [238]: df["cat"].cat.categories = [1,2,3,4,5]

In [239]: cat
Out[239]: 
[5, 5, 3, 5]
Categories (5, int64): [1, 2, 3, 4, 5]

使用copy=True可防止此类行为或仅仅不重复使用分类

In [240]: cat = pd.Categorical([1,2,3,10], categories=[1,2,3,4,10])

In [241]: s = pd.Series(cat, name="cat", copy=True)

In [242]: cat
Out[242]: 
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]

In [243]: s.iloc[0:2] = 10

In [244]: cat
Out[244]: 
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]

注意

在某些情况下,当你提供一个numpy数组而不是分类:使用一个int数组(例如np.array([1,2,3,4]))将显示相同的行为,同时使用字符串数组(例如np.array(["a","b","c","a"])

Scroll To Top