有没有一种有效的方法来检查列是否具有混合dtype?

时间:2018-12-12 15:11:25

标签: python pandas numpy dataframe typechecking

考虑

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

列“ A”具有混合数据类型。我想提出一种确定这一点的非常快速的方法。它不会像检查type == object那样简单,因为那样会将“ C”标识为假阳性。

我可以考虑使用

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 :(得分:13)

大熊猫中有infer_dtype(),在这里可能会有所帮助。

用Cython(code link)编写,它返回一个字符串,该字符串汇总了所传递对象中的值。它在熊猫内部使用了很多东西,因此我们可以合理地期望它的设计考虑到了效率。

>>> 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”类型就是“ mixed”:

>>> 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

答案 1 :(得分:5)

这是一种使用以下事实的方法:在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 :(得分:3)

不确定如何获得结果,但是可以将map typedf.values.ravel()并创建一个列名称的字典,该字典的链接指向{{1 }}中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)