Как заполнить пустой столбец в кадре данных определенным элементом из списка другого столбца?

1

Имея блок данных, состоящий из человека и заказа...

person     order                                 elements
Alice      [drink, snack, salad, fish, dessert]  5          
Tom        [drink, snack]                        2          
John       [drink, snack, soup, chicken]         4          
Mila       [drink, snack, soup]                  3          

Я хочу знать, что клиенты имели в качестве основного блюда. Таким образом, я хочу добавить еще один столбец [main_meal], так что это будет мой df.

person     order                               elements   main_meal
Alice      [drink, snack, salad, fish, dessert]  5          fish
Tom        [drink, snack]                        2          none
John       [drink, snack, soup, chicken]         4          chicken
Mila       [drink, snack, soup]                  3          none

Правило состоит в том, что если клиент заказал 4 или более приемов пищи, это означает, что 4-й элемент всегда является основным блюдом, поэтому я хочу извлечь 4-й элемент из списка в столбце заказа. Если он содержит менее 4 элементов, присвойте 'main_meal' никому. Мой код:

df['main_meal'] = ''
if df['elements'] >= 4:
     df['main_meal'] = df.order[3]
else:
     df['main_meal'] = 'none'

Это не работает:

 ValueError                                Traceback (most recent call last)
 <ipython-input-100-39b7809cc669> in <module>()
     1 df['main_meal'] = ''
     2 df.head(5)
 ----> 3 if df['elements'] >= 4:
       4     df['main_meal'] = df.order[3]
       5 else:

 ~\Anaconda\lib\site-packages\pandas\core\generic.py in __nonzero__(self)
 1571         raise ValueError("The truth value of a {0} is ambiguous. "
 1572                          "Use a.empty, a.bool(), a.item(), a.any() or 
 a.all()."
 -> 1573                          .format(self.__class__.__name__))
 1574 
 1575     __bool__ = __nonzero__

 ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

Что не так с моим кодом?

  • 0
    df['elements'] >= 4 - массив. Некоторые элементы могут быть истинными, некоторые могут быть ложными. Вы должны объяснить утверждение if как вы хотите справиться с этим делом.
  • 1
    @DYZ Я думаю, он имел в виду, if len(df['elements']) >= 4 , но он не векторизовал операцию, он думал по одной строке за раз.
Показать ещё 2 комментария
Теги:
pandas

3 ответа

4

Использовать метод str для среза

In [324]: df['order'].str[3]
Out[324]:
0       fish
1        NaN
2    chicken
3        NaN
Name: order, dtype: object

In [328]: df['main_meal'] = df['order'].str[3].fillna('none')

In [329]: df
Out[329]:
  person                                 order  elements main_meal
0  Alice  [drink, snack, salad, fish, dessert]         5      fish
1    Tom                        [drink, snack]         2      none
2   John         [drink, snack, soup, chicken]         4   chicken
3   Mila                  [drink, snack, soup]         3      none
1

Для небольших фреймов данных вы можете использовать аксессуар str в соответствии с решением @Zero. Для больших фреймов данных вы можете использовать представление NumPy для создания серии:

# Benchmarking on Python 3.6, Pandas 0.19.2

df = pd.concat([df]*100000)

%timeit pd.DataFrame(df['order'].values.tolist())[3]  # 125 ms per loop
%timeit df['order'].str[3]                            # 185 ms per loop

# check results align
x = pd.DataFrame(df['order'].values.tolist())[3].fillna('None').values
y = df['order'].str[3].fillna('None').values
assert (x == y).all()
0

Вы также можете использовать функции apply и lambda.

df['main_meal'] = df['order'].apply(lambda r: r[3] if len(r) >= 4 else 'none')

Это медленнее, чем ответ @jpp для больших наборов данных, но быстрее (и более подробный), чем ответы @jpp и @Zero для более мелких (обратите внимание, что я добавил .fillna() чтобы они возвращали тот же результат):

%timeit df['order'].apply(lambda r: r[3] if len(r) >= 4 else 'none')  # 242 µs
%timeit pd.DataFrame(df['order'].values.tolist())[3].fillna('none')  # 1.17 ms
%timeit df['order'].str[3].fillna('none')  # 487 µs

# Large dataset
df = pd.concat([df]*100000)

%timeit df['order'].apply(lambda r: r[3] if len(r) >= 4 else 'none')  # 118ms
%timeit pd.DataFrame(df['order'].values.tolist())[3].fillna('none')  # 51.8ms
%timeit df['order'].str[3].fillna('none')  # 309ms

И если вы проверите их значения, они будут совпадать.

x = df['order'].apply(lambda r: r[3] if len(r) >= 4 else 'none')
y = pd.DataFrame(df['order'].values.tolist())[3].fillna('none')
z = df['order'].str[3].fillna('none')

(x.values == y.values).all()  # True
(x.values == z.values).all()  # True

Python 3.6.6 | панды 0,23,4

Ещё вопросы

Сообщество Overcoder
Наверх
Меню