Категориальные данные
Новое в версии 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(categoriespandas). 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