Имея блок данных, состоящий из человека и заказа...
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().
Что не так с моим кодом?
Использовать метод 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
Для небольших фреймов данных вы можете использовать аксессуар 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()
Вы также можете использовать функции 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
df['elements'] >= 4
- массив. Некоторые элементы могут быть истинными, некоторые могут быть ложными. Вы должны объяснить утверждениеif
как вы хотите справиться с этим делом.if len(df['elements']) >= 4
, но он не векторизовал операцию, он думал по одной строке за раз.