Существует ли эффективный метод проверки, имеет ли столбец смешанные типы?

Рассматривать

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)? Я могу принять "Нет", если ваш аргумент достаточно убедителен. :-)

Ответы

Ответ 1

Вот подход, который использует тот факт, что в 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

Ответ 2

В infer_dtype() есть 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

Ответ 3

Не уверен, как вам нужен результат, но вы можете 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)

Ответ 4

Не думайте, что столбец может иметь другой тип данных. Я имею в виду, что он мог, но это было бы на гребне. Сам столбец будет иметь только один тип данных. Вы можете проверить это с помощью df.info()

Ответ 5

Я думаю, может использовать np.array без прохода dtype для тех смешанного типа будет список будет поднимать ошибку

l=[]
for x in  df.values.T.tolist():
    try :
        np.array(x)
        print(True)
        l.append(True)
    except Exception: 
        print( False)
        l.append(False)

False
True
True
l
Out[33]: [False, True, True]

Ответ 6

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

for i in df:
  tempdf = df[i][~df[i].isnull()].sort_values()
  first = type(tempdf.iloc[0])
  second = type(tempdf.iloc[-1])
  if first != second:
    print i

Поскольку здесь используется сортировка, это быстрая сортировка, поэтому она не должна занимать так много времени.

Надеюсь, это решит вашу проблему. Спасибо