【问题标题】:How to overcome 'NoneType' object has no attribute 'lower' error?如何克服 'NoneType' 对象没有属性 'lower' 错误?
【发布时间】:2018-10-05 18:34:29
【问题描述】:

我有DataFrame:

df=pd.DataFrame({'id':[1,2,3],'item1':['AK','CK',None],
'item2':['b','d','e'],'item3':['c','e',np.nan]})

我想将item1 列的所有值转换为小写。

我试过了:

df['item1'].apply(lambda x: x.lower())

这给了我一个错误:

AttributeError: 'NoneType' 对象没有属性 'lower'

我知道为什么会这样。我的列值之一是None

无论如何我想忽略该值并将其余值转换为小写。

有没有办法克服这个问题?

P.S:我原来的 DataFrame 可能有任意数量的值,因为它是由另一个函数返回的。删除行在这里不是一个案例,因为这些记录对我很重要。

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    很简单:

    df['item1'].apply(lambda x: x.lower() if x is not None else x)
    

    如果您想处理没有lower() 方法的其他可能类型(整数、浮点数等):

    df['item1'].apply(lambda x: x.lower() if hasattr(x, "lower") and callable(x.lower)  else x)
    

    【讨论】:

    • @AkshayNevrekar 不客气-但显然 Panda 知道如何以更简单(可能更有效)的方式处理这个问题(抱歉,我不是 Panda 用户,所以我发布了明显的'普通 python' 解决方案)。
    • NP。尽管在这种情况下我没有使用您的解决方案,但它可能会在将来对某人有所帮助,而且我们还学到了另一种选择:-)
    【解决方案2】:

    NoneNaNs 值的更通用解决方案是使用 notnull 函数,另一个解决方案是使用列表理解。

    pandas 字符串函数也可以很好地与 NoneNaNs 配合使用:

    df['new1'] = df['item1'].apply(lambda x: x.lower() if pd.notnull(x) else x)
    
    df['new2'] = [x.lower() if pd.notnull(x) else x for x in df['item1']]
    
    df['new3'] = df['item1'].str.lower()
    print (df)
       id item1 item2 item3  new1  new2  new3
    0   1    AK     b     c    ak    ak    ak
    1   2    CK     d     e    ck    ck    ck
    2   3  None     e   NaN  None  None  None
    

    df=pd.DataFrame({'id':[1,2,3],'item1':['AK',np.nan,None],
    'item2':['b','d','e'],'item3':['c','e',np.nan]})
    print (df)
       id item1 item2 item3
    0   1    AK     b     c
    1   2   NaN     d     e
    2   3  None     e   NaN
    
    df['new1'] = df['item1'].apply(lambda x: x.lower() if pd.notnull(x) else x)
    df['new2'] = [x.lower() if pd.notnull(x) else x for x in df['item1']]
    df['new3'] = df['item1'].str.lower()
    print (df)
       id item1 item2 item3  new1  new2  new3
    0   1    AK     b     c    ak    ak    ak
    1   2   NaN     d     e   NaN   NaN   NaN
    2   3  None     e   NaN  None  None  None
    

    如果不需要检查缺失值,大数据帧中的列表理解会更快:

    large = pd.Series([random.choice(string.ascii_uppercase) +
    random.choice(string.ascii_uppercase)
    for _ in range(100000)])
    
    In [275]: %timeit [x.lower() if pd.notnull(x) else x for x in large]
    73.3 ms ± 4.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    In [276]: %timeit large.str.lower()
    28.2 ms ± 684 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    In [277]: %timeit [x.lower() for x in large]
    14.1 ms ± 784 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    【讨论】:

      【解决方案3】:

      使用.str.* 函数,它们会自动知道忽略None 和空值(逐字包含):

      >>> df.item1.str.lower()
      0      ak
      1      ck
      2    None
      Name: item1, dtype: object
      

      这里不需要做任何选择。

      Working with Text Data documentation

      Series 和 Index 配备了一组字符串处理方法,可以方便地对数组的每个元素进行操作。也许最重要的是,这些方法会自动排除缺失/NA 值。

      来自Series.str documentation

      序列和索引的向量化字符串函数。除非通过特定方法处理,否则 NA 将保持 NA。

      尽管 Jezrael 建议您使用列表推导式,但这是一种错误的优化。对于只有 3 个值的极小样本系列,列表理解确实更快:

      >>> from timeit import Timer
      >>> import pandas as pd
      >>> tests = {}
      >>> tests['vectorised .str'] = 's.str.lower()'
      >>> tests['list comprehension'] = '[v.lower() if pd.notnull(v) else v for v in s]'
      >>> small = pd.Series(['AK', 'CK', None])
      >>> for name, test in tests.items():
      ...     count, totaltime = Timer(test, 'from __main__ import small as s, pd').autorange()
      ...     print(f'{name:>20}: {totaltime / count * 1000:.5f}ms')
      ...
           vectorised .str: 0.09495ms
        list comprehension: 0.01051ms
      

      几乎 10 倍的速度差异看起来令人印象深刻,但并不现实或显着(这两个时间之间只有 84 秒)

      一旦数据集变得更大(仅 250 行),向量化字符串操作就已经更快了:

      >>> larger = pd.Series([random.choice(string.ascii_uppercase) +
                              random.choice(string.ascii_uppercase)
      ...                     for _ in range(250)])  # 250 2-character uppercase strings
      >>> for name, test in tests.items():
      ...     count, totaltime = Timer(test, 'from __main__ import larger as s, pd').autorange()
      ...     print(f'{name:>20}: {totaltime / count * 1000:.5f}ms')
      ...
           vectorised .str: 0.15494ms
        list comprehension: 0.16758ms
      

      空值的比例在这里没有区别;这是一个示例时序,其中一半的行已设置为None

      >>> larger_1in2 = larger.copy()
      >>> large_1in2[np.random.random(large_1in2.shape) < 0.5] = None
      >>> for name, test in tests.items():
      ...     count, totaltime = Timer(test, 'from __main__ import larger_1in2 as s, pd').autorange()
      ...     print(f'{name:>20}: {totaltime / count * 1000:.5f}ms')
      ...
           vectorised .str: 0.14170ms
        list comprehension: 0.16098ms
      

      由于每 1 行 2 行中的比率为空,时间并没有发生重大变化。我尝试了不同的比率包括 100% 空值,速度差异没有动摇,250 行矢量化操作更快。

      但是向量化字符串操作和列表理解之间的差异只会随着更大的系列变得更大(有利于向量化字符串操作):

      >>> large = pd.Series([random.choice(string.ascii_uppercase) +
      ...                    random.choice(string.ascii_uppercase)
      ...                    for _ in range(1000)])
      >>> for name, test in tests.items():
      ...     count, totaltime = Timer(test, 'from __main__ import large as s, pd').autorange()
      ...     print(f'{name:>20}: {totaltime / count * 1000:.5f}ms')
      ...
           vectorised .str: 0.28704ms
        list comprehension: 0.56312ms
      

      你真的不会注意到小于 250 行的小型数据帧的性能差异,而在性能开始很重要的大型数据帧上,矢量化方法总是会更快。

      【讨论】:

      • @jezrael:你后来添加了那部分。您的第一个答案已被删除。我很惊讶 OP 没有这样做,布鲁诺也没有提到它。
      • @jezrael:这些都是矢量化的。您能否说明一些时间或链接到显示这些速度有多慢的帖子?
      • @jezrael:我确实可以看到列表 comp 的速度几乎是 10 倍。 .apply() 是最慢的。
      • 我尝试测试它,主要取决于 None 或 NaNs 的数量,如果大量此值 str.lower 在非常大的 DataFrame 中应该更快。在我看来,真实数据应该主要是字符串值,而不是 None 或 NaN,所以list comprehesnion 更快。
      • @jezrael:“真的很大”实际上一点也不大。例如,矢量化字符串操作在 250 行中击败了列表组合。 NA 的比例无关紧要。
      猜你喜欢
      • 2021-08-23
      • 2020-12-08
      • 2019-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多