Spec-Zone.ru › pandas 0.19

Категориальные данные

Новое в версии 0.15.

Примечание

Хотя в более ранних версиях существовал pandas.Categorical , возможность использования категориальных данных в Series и DataFrame является новой.

Это введение в тип данных категориальных данных pandas, включая краткое сравнение с factor R.

Categoricals — это тип данных pandas, который соответствует категориальным переменным в статистике: переменная, которая может принимать только ограниченное и обычно фиксированное количество возможных значений (categories; levels в R). Примеры включают пол, социальный класс, группы крови, принадлежность к стране, время наблюдения или рейтинги по шкалам Ликерта.

В отличие от статистических категориальных переменных, категориальные данные могут иметь порядок (например, «полностью согласен» против «согласен» или «первое наблюдение» против «второе наблюдение»), но числовые операции (сложение, деление и т. д.) невозможны.

Все значения категориальных данных находятся либо в categories , либо в np.nan. Порядок определяется порядком categories, а не лексическим порядком значений. Внутренне структура данных состоит из массива categories и целочисленного массива codes, которые указывают на реальное значение в массиве categories.

Тип данных категориальных данных полезен в следующих случаях:

  • Строковая переменная, состоящая только из нескольких различных значений. Преобразование такой строковой переменной в категориальную переменную позволит сэкономить память, см. здесь.
  • Лексический порядок переменной не совпадает с логическим порядком («один», «два», «три»). Преобразовав данные в категориальные и указав порядок категорий, сортировка и вычисление min/max будут использовать логический порядок вместо лексического, см. здесь.
  • Как сигнал другим библиотекам Python, что этот столбец должен обрабатываться как категориальная переменная (например, для использования соответствующих статистических методов или типов графиков).

См. также документацию API по категориальным данным.

Создание объекта

Категориальные Series или столбцы в DataFrame могут быть созданы несколькими способами:

Указывая dtype="category" при создании Series.

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]

Преобразуя существующий Series или столбец в тип category:

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().

Передав объект pandas.Categorical в Series или присвоив его 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 тип данных:

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

Примечание

В отличие от функции factor R, категориальные данные не преобразуют входные значения в строки, и категории останутся того же типа данных, что и исходные значения.

Примечание

В отличие от функции factor R, в настоящее время нет способа назначить/изменить метки во время создания. Используйте categories для изменения категорий после создания.

Для возврата к исходному Series или массиву 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)

Если у вас уже есть codes и categories, вы можете использовать конструктор from_codes(), чтобы сохранить шаг факторизации во время нормального режима конструктора:

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"]))

Описание

Применение .describe() к категориальным данным даст результат, аналогичный Series или DataFrame типа string.

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

Работа с категориями

Категориальные данные имеют свойства categories и ordered, которые перечисляют их возможные значения и указывает, важен ли порядок. Эти свойства представлены как s.cat.categories и s.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]

Переименование категорий

Переименование категорий выполняется путем присвоения новых значений свойству 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]

Примечание

В отличие от factor R, категориальные данные могут иметь категории других типов, кроме строк.

Примечание

Обратите внимание, что присвоение новых категорий — это операция на месте, в то время как большинство других операций в Series.cat по умолчанию возвращают новую Series типа category.

Категории должны быть уникальными, в противном случае возникает ValueError:

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

Добавление новых категорий

Добавление новых категорий выполняется с помощью метода 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]

Удаление категорий

Удаление категорий выполняется с помощью метода 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]

Удаление неиспользуемых категорий

Удаление неиспользуемых категорий также возможно:

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]

Установка категорий

Если вы хотите удалить и добавить новые категории в одном шаге (что обеспечивает некоторое преимущество в скорости) или просто установить категории на предварительно определенную шкалу, используйте 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() не может определить, была ли категория опущена намеренно или из-за опечатки, или (в Python3) из-за разницы в типах (например, S1-тип NumPy и строки Python). Это может привести к неожиданному поведению!

Сортировка и порядок

Предупреждение

По умолчанию при создании в версии 0.16.0 установлен ordered=False, вместо предыдущего неявного ordered=True.

Если категориальные данные упорядочены (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)

Переупорядочивание

Переупорядочивание категорий возможно с помощью методов 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, но если первая позиция была отсортирована последней, переименованное значение все равно будет отсортировано последним. Переупорядочивание означает, что способ сортировки значений отличается после этого, но не то, что индивидуальные значения в Series изменяются.

Примечание

Если категориальные данные не упорядочены, Series.min() и Series.max() вызовут TypeError. Числовые операции, такие как +, -, *, /, и операции, основанные на них (например, Series.median(), которые потребовали бы вычисления среднего значения между двумя значениями, если длина массива четная) не работают и вызывают TypeError.

Сортировка по нескольким столбцам

Столбец категориального типа данных будет участвовать в сортировке по нескольким столбцам аналогично другим столбцам. Порядок категорий определяется 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

Сравнения

Сравнение категориальных данных с другими объектами возможно в трех случаях:

  • сравнение на равенство (== и !=) с объектом, похожим на список (список, Series, массив, ...), длиной, равной длине категориальных данных.
  • все сравнения (==, !=, >, >=, <, и <=) категориальных данных с другим категориальным Series, когда ordered==True и categories совпадают.
  • все сравнения категориальных данных со скаляром.

Все остальные сравнения, особенно сравнения «неравенства» двух категориальных объектов с разными категориями или категориального объекта с любым объектом, похожим на список, приведут к ошибке TypeError.

Примечание

Любые сравнения «неравенства» категориальных данных с Series, np.array, list или категориальными данными с разными категориями или порядком приведут к ошибке 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)

Операции

Помимо Series.min(), Series.max() и Series.mode(), с категориальными данными возможны следующие операции:

Series методы, такие как 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

Обработка данных

Оптимизированные методы доступа к данным pandas .loc, .iloc, .ix .at, и .iat, работают как обычно. Единственное отличие — тип возвращаемого значения (для получения) и то, что можно назначать только значения, уже присутствующие в categories.

Получение

Если операция среза возвращает либо DataFrame, либо столбец типа Series, тип category сохраняется.

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 имеет тип 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'

Примечание

Это отличается от функции factor в R, где factor(c(1,2,3))[1] возвращает одно значение factor.

Чтобы получить одно значение Series типа category, передайте список с единственным значением:

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

Доступ к строковым и временным атрибутам

Введено в версии 0.17.1.

Атрибуты .dt и .str будут работать, если s.cat.categories имеют соответствующий тип:

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

Примечание

Возвращаемый Series (или DataFrame) имеет тот же тип, что и при использовании .str.<method> / .dt.<method> на Series такого типа (а не типа category!).

Это означает, что возвращаемые значения из методов и свойств атрибутов Series и возвращаемые значения из методов и свойств атрибутов этого Series, преобразованные в один из типа category, будут равны:

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 типа string, где много элементов повторяются (т. е. количество уникальных элементов в Series намного меньше длины Series). В этом случае может быть быстрее преобразовать исходный Series в один из типа category и использовать .str.<method> или .dt.<property> на нём.

Установка

Установка значений в категориальный столбец (или Series) работает, если значение включено в categories:

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

Установка значений путём присваивания категориальных данных также проверяет, что categories совпадают:

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

Присваивание Categorical частям столбца других типов будет использовать значения:

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

Объединение

Можно объединить два 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).

Объединение категориальных данных

Введено в версии 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 также работает с CategoricalIndex, или Series содержащими категориальные данные, но обратите внимание, что результирующий массив всегда будет обычным массивом 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)

Конкатенация категориальных данных

Этот раздел описывает конкатенацию, специфичную для типа category. См. Объединение объектов для общего описания.

Конкатенация Series или DataFrame, содержащих одинаковые категории, по умолчанию приводит к типу category, в противном случае к типу object. Используйте .astype или union_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 результат
категория категория (идентичные категории) категория
категория категория (разные категории, обе не упорядочены) объект (тип определяется по умолчанию)
категория категория (разные категории, хотя бы одна упорядочена) объект (тип определяется по умолчанию)
категория не категория объект (тип определяется по умолчанию)

Ввод/вывод данных

Введено в версии 0.15.2.

Запись данных (Series, Frames) в хранилище HDF, содержащее тип category данных, была реализована в 0.15.2. См. здесь для примера и ограничений.

Запись данных в и чтение данных из файлов формата Stata были реализованы в 0.15.2. См. здесь для примера и ограничений.

Запись в CSV-файл преобразует данные, фактически удаляя информацию о категориальных данных (категории и порядок). Поэтому, если вы читаете обратно CSV-файл, вам нужно преобразовать соответствующие столбцы обратно в category и назначить правильные категории и порядок категорий.

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]

То же самое относится к записи в базу данных SQL с to_sql.

Пропущенные данные

pandas в основном использует значение np.nan для представления пропущенных данных. По умолчанию оно не учитывается при расчётах. См. раздел Пропущенные данные.

Пропущенные значения не должны включаться в categories, только в 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]

Отличия от R's factor

Следующие различия можно наблюдать между функциями R's factor:

  • Значения R levels называются categories
  • Значения R levels всегда имеют тип строка, в то время как значения categories в pandas могут иметь любой тип данных.
  • Невозможно указать метки при создании. Используйте s.cat.rename_categories(new_labels) позже.
  • В отличие от функции R factor, использование категориальных данных в качестве единственного входного значения для создания новой категориальной серии не удалит неиспользуемые категории, а создаст новую категориальную серию, равную переданной!
  • R позволяет включать пропущенные значения в свои levels (categories pandas). Pandas не допускает NaN категорий, но пропущенные значения все еще могут быть в values.

Особенности

Использование памяти

Использование памяти Categorical пропорционально количеству категорий, умноженному на длину данных. В отличие от этого, тип object имеет размер, постоянный относительно длины данных.

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.

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

Использование конструктора старого стиля

В более ранних версиях pandas (до 0.15) Categorical можно было создать, передав предварительно вычисленные codes (тогда называемые labels) вместо значений с категориями. codes интерпретировались как указатели на категории, а -1 как NaN. Этот тип использования конструктора заменен специальным конструктором Categorical.from_codes().

К сожалению, в некоторых особых случаях использование кода, предполагающего использование конструктора старого стиля, будет работать с текущей версией pandas, что приведет к скрытым ошибкам:

>>> 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)

Предупреждение

Если вы использовали Categoricals с более старыми версиями pandas, проверьте свой код перед обновлением и измените его на использование конструктора from_codes().

Categorical не является массивом numpy

В настоящее время категориальные данные и лежащий в их основе Categorical реализованы как объект Python, а не как низкоуровневый тип данных массива numpy. Это приводит к некоторым проблемам.

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

Сравнения типов данных работают:

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

Использование функций numpy для Series типа category не должно работать, так как Categoricals не являются числовыми данными (даже в том случае, если .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 в apply

Pandas в настоящее время не сохраняет тип данных в функциях apply: Если вы применяете вдоль строк, вы получаете Series типа 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

Категориальный индекс

Введено в версии 0.16.1.

Новый тип индекса CategoricalIndex введён в версии 0.16.1. Более подробное объяснение см. в документации по индексированию.

Установка индекса создаст 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

В предыдущих версиях (<0.16.1) нет индекса типа category, поэтому установка индекса на категориальный столбец предварительно преобразует категориальные данные в «обычный» тип данных, удаляя при этом любое пользовательское упорядочение категорий.

Побочные эффекты

Создание Series из Categorical не копирует входной Categorical. Это означает, что изменения в Series в большинстве случаев изменят исходный Categorical:

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 чтобы предотвратить такое поведение или просто не используйте Categoricals повторно:

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 вместо массива Categorical: использование массива целых чисел (например, np.array([1,2,3,4])) будет иметь то же поведение, в то время как использование массива строк (например, np.array(["a","b","c","a"])) не будет.

© 2008–2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/0.19.2/categorical.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API