Получить определенную строку как серию из pandas dataframe
Как мы можем получить определенную отфильтрованную строку как серию?
Пример dataframe:
>>> df = pd.DataFrame({'date': [20130101, 20130101, 20130102], 'location': ['a', 'a', 'c']})
>>> df
date location
0 20130101 a
1 20130101 a
2 20130102 c
Мне нужно выбрать строку, в которой location
есть c
как серия.
Я пробовал:
row = df[df["location"] == "c"].head(1) # gives a dataframe
row = df.ix[df["location"] == "c"] # also gives a dataframe with single row
В обоих случаях я не могу использовать ряд как строку.
Ответы
Ответ 1
Используйте функцию squeeze
, которая удалит одно измерение из кадра данных:
df[df["location"] == "c"].squeeze()
Out[5]:
date 20130102
location c
Name: 2, dtype: object
DataFrame.squeeze
метод действует один и тот же путь squeeze
аргумента read_csv
функции, если задано значение True
: если в результате dataframe является 1-Len dataframe, т.е. она имеет только одно измерение (столбец или строку), то объект сжал до объекта меньшего размера.
В вашем случае вы получаете объект Series из DataFrame. Та же логика применяется, если вы сжимаете Panel вниз в DataFrame.
squeeze явно присутствует в вашем коде и ясно показывает ваше намерение "бросить" объект в руки, потому что его размерность можно спроецировать на меньший.
Если в кадре данных более одного столбца или строки, сжатие не имеет никакого эффекта.
Ответ 2
Вы можете просто взять первую строку с целым индексированием (функция iloc()):
>>> df[df["location"] == "c"].iloc[0]
date 20130102
location c
Name: 2, dtype: object
Ответ 3
Как я могу получить конкретную строку в виде серии из pandas DataFrame?
Надежное решение: DataFrame.iloc
с Series.idxmax
В качестве лучшей альтернативы, если вы можете гарантировать, что хотя бы одна строка соответствует условию, используйте Series.idxmax()
для маски и сделайте это с помощью одного вызова DataFrame.iloc
.
df.iloc[(df['location'] == 'c').idxmax()]
date 20130102
location c
Name: 2, dtype: object
Возможно, это лучшая альтернатива текущим опубликованным ответам, потому что она гарантирует возврат одной (и только одной) строки, а также никогда не делает копию.
Критика других ответов
В принятом ответе только мимоходом было упомянуто, что squeeze
имеет никакого эффекта, если возвращено более одной строки, но это проблема
df
date location
0 20130101 a
1 20130101 a
2 20130102 c
df[df["location"] == "c"].squeeze() # Works as expected.
date 20130102
location c
Name: 2, dtype: object
Теперь рассмотрим, когда более чем один ряд соответствует этому условию.
df2 = pd.concat([df] * 2, ignore_index=True)
df2
date location
0 20130101 a
1 20130101 a
2 20130102 c
3 20130101 a
4 20130101 a
5 20130102 c
df2[df2["location"] == "c"].squeeze() # No effect.
date location
2 20130102 c
5 20130102 c
При использовании idxmax
индекс первой строки с наибольшим значением в результате "location"] == "c"
(что соответствует True
если хотя бы одна строка удовлетворяет условию). Таким образом, вы получаете серию каждый раз.
Далее, ответ @RomanPekar использует iloc
в результате логического вызова индексации, который может возвращать или не возвращать копию. Не говоря уже о том, что это становится проблемой, если вы пытаетесь назначить новую строку обратно:
df[df["location"] == "c"].iloc[0] = pd.Series({'location': 'd', 'date': np.nan})
# SettingWithCopyWarning:
# A value is trying to be set on a copy of a slice from a DataFrame.
# Try using .loc[row_indexer,col_indexer] = value instead
Вы получаете SettingWithCopyWarning
(вы можете прочитать об этом здесь).
Это не проблема, если вы используете один вызов iloc
:
df.iloc[(df['location'] == 'c').idxmax()] = (
pd.Series({'location': 'd', 'date': np.nan}))
df
date location
0 20130101.0 a
1 20130101.0 a
2 NaN d
Пусть покупатель будет бдителен
idxmax
вернет индекс первой строки, который является True
в результате df['location'] == 'c'
:
df2.iloc[(df2['location'] == 'c').idxmax()]
date 20130102
location c
Name: 2, dtype: object
Но предостережение здесь видно, когда вообще нет рядов. idxmax
всегда будет просто возвращать индекс первой строки (поскольку значение в первой строке равно False
, наибольшее значение в маске).
df3 = df.query('location == "a"')
df3
date location
0 20130101 a
1 20130101 a
# This will produce an incorrect result.
df3.iloc[(df3['location'] == 'c').idxmax()]
date 20130101
location a
Name: 0, dtype: object
Таким образом, вы можете добавить код обработки ошибок для обработки этих угловых случаев. Мое предложение - это встроенное утверждение if-else для краткости:
df3.iloc[mask.idxmax()] if mask.any() else None
Некоторые примеры,
# Correct handling of corner case.
m = df3['location'] == 'c'
ser = df3.iloc[m.idxmax()] if m.any() else None
print(ser)
# None
# Correct handling of the standard case.
m = df3['location'] == 'a'
df3.iloc[m.idxmax()] if m.any() else None
date 20130101
location a
Name: 0, dtype: object