Как преодолеть «NoneType» объект не имеет атрибута «нижняя» ошибка? - PullRequest
0 голосов
/ 26 сентября 2018

У меня есть 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.

Я хочу в любом случае игнорировать это значение и преобразовать остальные значения в нижний регистр.

Есть ли способ преодолеть это?

PS: Мой оригинал DataFrame может иметь любое количество значений, так как он возвращается другой функцией.Сброс строки здесь не тот случай, так как эти записи важны для меня.

Ответы [ 3 ]

0 голосов
/ 26 сентября 2018

Более общее решение для значений None и NaN s - использование функции notnull, другое решение - использование понимания списка.

Также очень хорошо работают строковые функции панд с None и * 1007.* s:

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)
0 голосов
/ 26 сентября 2018

Используйте функции .str.*, которые автоматически распознают игнорирование как None, так и нулевых значений (они включены дословно):

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

Нет необходимости делать какой-либо выбор здесь.

См. Работа с текстовыми данными Документация :

Серия и индекс оснащены набором методов обработки строк, облегчающих егоработать с каждым элементом массива.Возможно, наиболее важно то, что эти методы автоматически исключают пропущенные значения / значения NA.

и из Series.str документации ;

Векторизованные строковые функции для Seriesи индекс.NA остаются NA, если не обрабатываются каким-либо другим способом определенным способом.

Хотя «Джезраэль» предлагает вам использовать понимание списка, это ложная оптимизация.Для вашей чрезвычайно малой серии выборок только с 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 * 1030)* микро секунд между этими двумя временами)

Как только набор данных становится немного больше (всего 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 строк и на больших фреймах данных, где производительность фактически начинает растиВ любом случае, векторизованный метод всегда будет быстрее. </p>

0 голосов
/ 26 сентября 2018

Проще говоря:

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...