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