Существует ли эффективный метод проверки наличия в столбце смешанных типов? - PullRequest
0 голосов
/ 12 декабря 2018

Рассмотрим

np.random.seed(0)
s1 = pd.Series([1, 2, 'a', 'b', [1, 2, 3]])
s2 = np.random.randn(len(s1))
s3 = np.random.choice(list('abcd'), len(s1))


df = pd.DataFrame({'A': s1, 'B': s2, 'C': s3})
df
           A         B  C
0          1  1.764052  a
1          2  0.400157  d
2          a  0.978738  c
3          b  2.240893  a
4  [1, 2, 3]  1.867558  a

Столбец «А» имеет смешанные типы данных.Я хотел бы найти действительно быстрый способ определить это.Это было бы не так просто, как проверить, является ли type == object, потому что это идентифицировало бы «С» как ложное срабатывание.

Я могу подумать сделать это с помощью

df.applymap(type).nunique() > 1

A     True
B    False
C    False
dtype: bool

Но вызов type поверх applymap довольно медленно.Особенно для больших кадров.

%timeit df.applymap(type).nunique() > 1
3.95 ms ± 88 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Можем ли мы добиться большего успеха (возможно, с NumPy)?Я могу принять «Нет», если ваш аргумент достаточно убедителен.: -)

Ответы [ 3 ]

0 голосов
/ 12 декабря 2018

В пандах есть infer_dtype(), которые могут быть здесь полезны.

Написано на Cython ( кодовая ссылка ), возвращает строку, суммирующую значения впереданный объект.Он часто используется во внутренних органах панд, поэтому мы можем разумно ожидать, что он был разработан с учетом эффективности.

>>> from pandas.api.types import infer_dtype

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

>>> infer_dtype(df.A)
'mixed-integer'

Все значения столбца B имеют плавающий тип:

>>> infer_dtype(df.B)
'floating'

Столбец C содержит строки:

>>> infer_dtype(df.B)
'string'

Общий тип "catchall" для смешанных значений просто "смешанный"":

>>> infer_dtype(['a string', pd.Timedelta(10)])
'mixed'

Сочетание чисел с плавающей точкой и целых чисел это '' mixed-integer-float '':

>>> infer_dtype([3.141, 99])
'mixed-integer-float'

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

def is_mixed(col):
    return infer_dtype(col) in ['mixed', 'mixed-integer']

Тогда у вас есть:

>>> df.apply(is_mixed)
A     True
B    False
C    False
dtype: bool
0 голосов
/ 13 декабря 2018

Вот подход, использующий тот факт, что в Python3 нельзя сравнивать разные типы.Идея состоит в том, чтобы запустить max по массиву, который должен быть достаточно быстрым.И это делает короткую цепочку.

def ismixed(a):
    try:
        max(a)
        return False
    except TypeError as e: # we take this to imply mixed type
        msg, fst, and_, snd = str(e).rsplit(' ', 3)
        assert msg=="'>' not supported between instances of"
        assert and_=="and"
        assert fst!=snd
        return True
    except ValueError as e: # catch empty arrays
        assert str(e)=="max() arg is an empty sequence"
        return False

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

Но это достаточно быстро.Если отбросить все pandas накладные расходы:

v = df.values

list(map(is_mixed, v.T))
# [True, False, False]
timeit(lambda: list(map(ismixed, v.T)), number=1000)
# 0.008936170022934675

Для сравнения

timeit(lambda: list(map(infer_dtype, v.T)), number=1000)
# 0.02499613002873957
0 голосов
/ 12 декабря 2018

Не знаю, как вам нужен результат, но вы можете map от type до df.values.ravel() и создать словарь с названием ссылки на столбец для сравнения len с set улучшеннымдо 1 для каждого среза l, например:

l = list(map(type, df.values.ravel()))
print ({df.columns[i]:len(set(l[i::df.shape[1]])) > 1 for i in range(df.shape[1])})
{'A': True, 'B': False, 'C': False}

Время:

%timeit df.applymap(type).nunique() > 1
#3.25 ms ± 516 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit 
l = list(map(type, df.values.ravel()))
{df.columns[i]:len(set(l[i::df.shape[1]])) > 1 for i in range(df.shape[1])}
#100 µs ± 5.08 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

РЕДАКТИРОВАТЬ для более крупного кадра данных, хотя улучшение во времени менее интересно:

dfl = pd.concat([df]*100000,ignore_index=True)

%timeit dfl.applymap(type).nunique() > 1
#519 ms ± 61.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
l = list(map(type, dfl.values.ravel()))
{dfl.columns[i]:len(set(l[i::dfl.shape[1]])) > 1 for i in range(dfl.shape[1])}
#254 ms ± 33.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Немного более быстрое решение по той же идее:

%timeit { col: len(set(map(type, dfl[col])))>1 for col in dfl.columns}
#124 ms ± 15.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
...