Как изменить несколько столбцов Pandas DF на категориальные без цикла

У меня есть DataFrame, в котором я хочу изменить несколько столбцов с типа «объект» на «категорию».

Я могу изменить несколько столбцов одновременно для float,

dftest[['col3', 'col4', 'col5', 'col6']] = \
    dftest[['col3', 'col4', 'col5', 'col6']].astype(float)

Для «категории» я не могу сделать то же самое, мне нужно делать это по одному (или в цикле, например здесь ).

for col in ['col1', 'col2']:
    dftest[col] = dftest[col].astype('category')

Вопрос: Есть ли способ внести изменения сразу во все нужные столбцы, как в примере с плавающей запятой?

Если я попытаюсь сделать несколько столбцов одновременно, у меня будет:

dftest[['col1','col2']] = dftest[['col1','col2']].astype('category')
## NotImplementedError: > 1 ndim Categorical are not supported at this time

Мой текущий рабочий тестовый код:

import numpy as np
import pandas as pd 

factors= np.array([
        ['a', 'xx'],
        ['a', 'xx'],
        ['ab', 'xx'],
        ['ab', 'xx'],
        ['ab', 'yy'],
        ['cc', 'yy'],
        ['cc', 'zz'],
        ['d', 'zz'],
        ['d', 'zz'],
        ['g', 'zz'] 
        ])

values = np.random.randn(10,4).round(2)

dftest = pd.DataFrame(np.hstack([factors,values]), 
                  columns = ['col1', 'col2', 'col3', 'col4', 'col5', 'col6'])

#dftest[['col1','col2']] = dftest[['col1','col2']].astype('category')
## NotImplementedError: > 1 ndim Categorical are not supported at this time

## it works with individual astype
#dftest['col2'] = dftest['col2'].astype('category')
#dftest['col1'] = dftest['col1'].astype('category')

print(dftest)

## doing a loop
for col in ['col1', 'col2']:
    dftest[col] = dftest[col].astype('category')


dftest[['col3', 'col4', 'col5', 'col6']] = \
    dftest[['col3', 'col4', 'col5', 'col6']].astype(float)

dftest.dtypes

выход:

col1    category
col2    category
col3     float64
col4     float64
col5     float64
col6     float64
dtype: object

== [обновление] ==

У меня нет проблем с использованием цикла теперь, когда я знаю трюк, но я задал вопрос, потому что хотел узнать/понять, ПОЧЕМУ мне нужно сделать цикл для «категории», а не для числа с плавающей запятой, если нет другого способ сделать это.


person Pablo Marin-Garcia    schedule 29.08.2016    source источник
comment
Из любопытства, в чем смысл? Скорость?   -  person IanS    schedule 29.08.2016
comment
@IanS смотрите мое обновление   -  person Pablo Marin-Garcia    schedule 29.08.2016
comment
Не реализовано, как правило, означает, что это запланировано для будущей версии. Категориалы относительно новы, поэтому мы можем ожидать, что .astype('category) будет работать для более чем 1 столбца в будущем.   -  person ayhan    schedule 31.08.2016


Ответы (2)


Не сразу понятно, каким должен быть результат dftest[['col1','col2']].astype('category'), то есть должны ли результирующие столбцы иметь одни и те же категории или нет.

Перебор столбцов приводит к тому, что каждый столбец имеет отдельный набор категорий. (Я считаю, что это желаемый результат в вашем примере.)

С другой стороны, .astype(float) работает по-другому: он преобразует базовые значения в массив 1d, преобразует их в числа с плавающей запятой, а затем возвращает их к исходной форме. Таким образом, это может быть быстрее, чем просто перебор столбцов. Вы можете эмулировать это поведение для category с помощью функций более высокого уровня:

result = dftest[['col1', 'col2']].stack().astype('category').unstack()

Но тогда вы получите единый набор категорий, разделяемых обоими столбцами:

result['col1']
Out[36]: 
0     a
1     a
2    ab
3    ab
4    ab
5    cc
6    cc
7     d
8     d
9     g
Name: col1, dtype: category
Categories (8, object): [a < ab < cc < d < g < xx < yy < zz]
person ptrj    schedule 30.08.2016
comment
Спасибо @ptrj, теперь я вижу, что проблема не в приведении к другому типу, а в том, как обрабатывать уровни в случае категориальных столбцов. Я не думал об этом, но теперь для меня это имеет смысл. Спасибо за просвещение. - person Pablo Marin-Garcia; 31.08.2016

вы можете сделать это следующим образом:

In [99]: pd.concat([dftest[['col1', 'col2']].apply(lambda x: x.astype('category')), dftest.ix[:, 'col3':].astype('float')], axis=1)
Out[99]:
  col1 col2  col3  col4  col5  col6
0    a   xx  0.30  2.28  0.84  0.31
1    a   xx -0.13  2.04  2.62  0.49
2   ab   xx -0.34 -0.32 -1.87  1.49
3   ab   xx -1.18 -0.57 -0.57  0.87
4   ab   yy  0.66  0.65  0.96  0.07
5   cc   yy  0.88  2.43  0.76  1.93
6   cc   zz  1.81 -1.40 -2.29 -0.13
7    d   zz -0.05  0.60 -0.78 -0.28
8    d   zz -0.36  0.98  0.23 -0.17
9    g   zz -1.31 -0.84  0.02  0.47

In [100]: pd.concat([dftest[['col1', 'col2']].apply(lambda x: x.astype('category')), dftest.ix[:, 'col3':].astype('float')], axis=1).dtypes
Out[100]:
col1    category
col2    category
col3     float64
col4     float64
col5     float64
col6     float64
dtype: object

но это не будет намного быстрее, так как метод apply() использует зацикливание под капотом

person MaxU    schedule 29.08.2016
comment
Спасибо @MaxU, но цель вопроса была больше о том, почему я не могу изменить тип на категорию для нескольких столбцов, как в float? Я хотел знать, было ли это ограничением моих знаний о пандах. Посмотреть обновление - person Pablo Marin-Garcia; 29.08.2016