Spec-Zone.ru › pandas 0.18

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

Новое в версии 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 dtype:

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() для сохранения шага 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"]))

Описание

Применение .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() не может определить, является ли пропуск категории преднамеренным или из-за неправильного написания или (в Python 3) из-за разницы типов (например, тип данных 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

Группировка также отобразит «неиспользуемые» категории:

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'

Примечание

Это отличие от функции R’s factor, где 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 типа строка, где много элементов повторяется (т.е. число уникальных элементов в 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))
   .....: 
ValueError: incompatible categories in categorical concat

То же самое относится к df.append(df_different).

Получение данных В/Из

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

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

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

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

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

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

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

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

In [165]: csv = StringIO()

In [166]: df.to_csv(csv)

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

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

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

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

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

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

In [173]: df2["cats"]
Out[173]: 
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 [174]: s = pd.Series(["a", "b", np.nan, "a"], dtype="category")

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

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

Методы работы с пропущенными данными, например, isnull(), fillna(), dropna(), работают нормально:

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

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

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

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

Отличия от функции R’s factor

Можно наблюдать следующие отличия от функций R’s factor:

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

Особенности

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

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

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

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

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

Примечание

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

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

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

# category dtype
In [186]: s.astype('category').nbytes
Out[186]: 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 [187]: try:
   .....:     np.dtype("category")
   .....: except TypeError as e:
   .....:     print("TypeError: " + str(e))
   .....: 
TypeError: data type "category" not understood

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

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

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

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

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

Чтобы проверить, содержит ли Series категориальные данные, с pandas 0.16 или более поздней версией, используйте hasattr(s, 'cat'):

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

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

Использование функций numpy на Series типа category не должно работать, так как Categoricals — это не числовые данные (даже в случае, если .categories числовые).

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

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

Примечание

Если такая функция работает, пожалуйста, подайте ошибку на https://github.com/pydata/pandas!

dtype в apply

Pandas в настоящее время не сохраняет тип данных в функциях apply: Если вы применяете функцию по строкам, вы получаете Series типа object dtype (так же, как получение строки —> получение одного элемента вернет базовый тип), а применение по столбцам также преобразует в объект.

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

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

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

Индекс Categorical

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

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

Установка индекса создаст CategoricalIndex

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Используйте copy=True для предотвращения такого поведения или просто не используйте Categoricals:

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

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

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

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

In [217]: cat
Out[217]: 
[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.18.1/categorical.html

Spec-Zone.ru

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