#python #pandas #dataframe
#python #pandas #фрейм данных
Вопрос:
Учитывая фрейм данных, подобный приведенному ниже, вот что я хочу: только в строках, содержащих самую раннюю дату для каждого серийного номера, найдите строки, где Location равно null, и обновите их указанным значением по умолчанию.
df = pd.DataFrame([['123456',pd.to_datetime('1/1/2019'),'Location A'],
['123456',pd.to_datetime('1/2/2019'),np.nan],
['123456',pd.to_datetime('1/3/2019'),np.nan],
['123456',pd.to_datetime('5/1/2019'),np.nan],
['654321',pd.to_datetime('2/1/2019'),'Location B'],
['654321',pd.to_datetime('2/2/2019'),'Location B'],
['654321',pd.to_datetime('2/3/2019'),'Location C'],
['112233',pd.to_datetime('3/1/2019'),np.nan],
['112233',pd.to_datetime('3/2/2019'),'Location D'],
['112233',pd.to_datetime('3/3/2019'),np.nan],
['445566',pd.to_datetime('4/1/2019'),'Location E'],
['445566',pd.to_datetime('4/2/2019'),'Location E'],
['445566',pd.to_datetime('4/3/2019'),'Location E'],
['778899',pd.to_datetime('5/1/2019'),np.nan],
['778899',pd.to_datetime('5/2/2019'),np.nan],
['778899',pd.to_datetime('5/3/2019'),np.nan],
['332211',pd.to_datetime('6/1/2019'),np.nan],
['332211',pd.to_datetime('6/2/2019'),'Location F'],
['332211',pd.to_datetime('6/3/2019'),'Location F'],
['665544',pd.to_datetime('7/1/2019'),'Location G'],
['665544',pd.to_datetime('7/2/2019'),'Location G'],
['665544',pd.to_datetime('7/3/2019'),'Location G'],
['998877',pd.to_datetime('8/1/2019'),'Location H'],
['998877',pd.to_datetime('8/2/2019'),'Location I'],
['998877',pd.to_datetime('8/2/2019'),'Location I'],
['147258',pd.to_datetime('9/1/2019'),np.nan],
['147258',pd.to_datetime('9/2/2019'),np.nan],
['147258',pd.to_datetime('9/3/2019'),'Location J']],
columns=['Serial','Date','Location'])
df
Out[498]:
Serial Date Location
0 123456 2019-01-01 Location A
1 123456 2019-01-02 NaN
2 123456 2019-01-03 NaN
3 123456 2019-05-01 NaN
4 654321 2019-02-01 Location B
5 654321 2019-02-02 Location B
6 654321 2019-02-03 Location C
7 112233 2019-03-01 NaN
8 112233 2019-03-02 Location D
9 112233 2019-03-03 NaN
10 445566 2019-04-01 Location E
11 445566 2019-04-02 Location E
12 445566 2019-04-03 Location E
13 778899 2019-05-01 NaN
14 778899 2019-05-02 NaN
15 778899 2019-05-03 NaN
16 332211 2019-06-01 NaN
17 332211 2019-06-02 Location F
18 332211 2019-06-03 Location F
19 665544 2019-07-01 Location G
20 665544 2019-07-02 Location G
21 665544 2019-07-03 Location G
22 998877 2019-08-01 Location H
23 998877 2019-08-02 Location I
24 998877 2019-08-02 Location I
25 147258 2019-09-01 NaN
26 147258 2019-09-02 NaN
27 147258 2019-09-03 Location J
Итак, в приведенном выше примере должны быть выбраны только строки 6, 12, 15 и 24. У меня это работает со строкой ниже, которая:
- Использует groupby для получения списка индексов минимальных дат
- Сравнивает это с индексом df, возвращает логический ряд
- Проверяет наличие любых нулей в столбце Location, возвращает другой логический ряд
- Сравнивает два логических ряда, возвращает окончательный логический ряд
- Наконец, выбирает записи местоположения на основе этого окончательного логического ряда
Несмотря на функциональность, это кажется неуклюжим и обходным. Есть ли способ лучше?
df.loc[pd.Series(df.index).isin(df.groupby('Serial')['Date'].idxmin().tolist()) amp; df['Location'].isnull(), 'Location'] = 'XXXX'
df
Out[502]:
Serial Date Location
0 123456 2019-01-01 Location A
1 123456 2019-01-02 NaN
2 123456 2019-01-03 NaN
3 123456 2019-05-01 NaN
4 654321 2019-02-01 Location B
5 654321 2019-02-02 Location B
6 654321 2019-02-03 Location C
7 112233 2019-03-01 XXXX
8 112233 2019-03-02 Location D
9 112233 2019-03-03 NaN
10 445566 2019-04-01 Location E
11 445566 2019-04-02 Location E
12 445566 2019-04-03 Location E
13 778899 2019-05-01 XXXX
14 778899 2019-05-02 NaN
15 778899 2019-05-03 NaN
16 332211 2019-06-01 XXXX
17 332211 2019-06-02 Location F
18 332211 2019-06-03 Location F
19 665544 2019-07-01 Location G
20 665544 2019-07-02 Location G
21 665544 2019-07-03 Location G
22 998877 2019-08-01 Location H
23 998877 2019-08-02 Location I
24 998877 2019-08-02 Location I
25 147258 2019-09-01 XXXX
26 147258 2019-09-02 NaN
27 147258 2019-09-03 Location J
РЕДАКТИРОВАТЬ: Добавлена новая строка 3 в образец df, чтобы уточнить, что даты уникальны в группах серийных номеров, но могут не быть уникальными в сериалах. Строка с индексом 3 в этом примере имеет ту же дату, что и минимальная дата другого серийного номера, но ее не следует выбирать. Я справился с этим, сопоставив индексы вместо самих дат, но способ, которым я это сделал, кажется беспорядочным.
Ответ №1:
Я думаю, что ваше решение «нормально», но вы могли бы сделать его немного более жестким и ускорить его с помощью numpy
.
Вы можете использовать df.groupby.Series.min()
для этого и df.Series.isnull()
.
После этого вы условно заполняете свой Location
столбец XXXX
с np.where
:
min_date = df.groupby('Serial')['Date'].min()
cond = df['Location'].isnull()
df['Location'] = np.where((df['Date'].isin(min_date)) amp; (cond) , 'XXXX', df['Location'])
print(df)
Serial Date Location
0 123456 2019-01-01 Location A
1 123456 2019-01-02 NaN
2 123456 2019-01-03 NaN
3 654321 2019-02-01 Location B
4 654321 2019-02-02 Location B
5 654321 2019-02-03 Location C
6 112233 2019-03-01 XXXX
7 112233 2019-03-02 Location D
8 112233 2019-03-03 NaN
9 445566 2019-04-01 Location E
10 445566 2019-04-02 Location E
11 445566 2019-04-03 Location E
12 778899 2019-05-01 XXXX
13 778899 2019-05-02 NaN
14 778899 2019-05-03 NaN
15 332211 2019-06-01 XXXX
16 332211 2019-06-02 Location F
17 332211 2019-06-03 Location F
18 665544 2019-07-01 Location G
19 665544 2019-07-02 Location G
20 665544 2019-07-03 Location G
21 998877 2019-08-01 Location H
22 998877 2019-08-02 Location I
23 998877 2019-08-02 Location I
24 147258 2019-09-01 XXXX
25 147258 2019-09-02 NaN
26 147258 2019-09-03 Location J
Редактировать после комментария OP о повторяющихся датах:
Мы можем объединить min_dates
фрейм данных и использовать indicator=True
при объединении
min_date = df.groupby('Serial')['Date'].min().reset_index()
cond = df['Location'].isnull()
df = df.merge(min_date, on=['Serial', 'Date'], how='left', indicator=True)
df['Location'] = np.where((df['_merge'] == 'both') amp; (cond) , 'XXXX', df['Location'])
df = df.drop('_merge', axis=1)
print(df)
Serial Date Location
0 123456 2019-01-01 Location A
1 123456 2019-01-02 NaN
2 123456 2019-01-03 NaN
3 123456 2019-05-01 NaN
4 654321 2019-02-01 Location B
5 654321 2019-02-02 Location B
6 654321 2019-02-03 Location C
7 112233 2019-03-01 XXXX
8 112233 2019-03-02 Location D
9 112233 2019-03-03 NaN
10 445566 2019-04-01 Location E
11 445566 2019-04-02 Location E
12 445566 2019-04-03 Location E
13 778899 2019-05-01 XXXX
14 778899 2019-05-02 NaN
15 778899 2019-05-03 NaN
16 332211 2019-06-01 XXXX
17 332211 2019-06-02 Location F
18 332211 2019-06-03 Location F
19 665544 2019-07-01 Location G
20 665544 2019-07-02 Location G
21 665544 2019-07-03 Location G
22 998877 2019-08-01 Location H
23 998877 2019-08-02 Location I
24 998877 2019-08-02 Location I
25 147258 2019-09-01 XXXX
26 147258 2019-09-02 NaN
27 147258 2019-09-03 Location J
Комментарии:
1. Извините, чтобы немного прояснить: даты уникальны в пределах каждого серийного номера, но не обязательно могут быть уникальными для серийных номеров. Итак, если бы я добавил следующую запись в этот пример df:
['123456',pd.to_datetime('5/1/2019'),np.nan]
Ваш код выбрал бы эту новую строку из-за строки 12 в примере df, которая мне не нужна. Следовательно, почему я использовал idxmin вместо просто min.2. Хотя мне нравится, как вы используете np.where.
3. Не уверен, что это не неуклюжий или обходной путь, но это другой способ, которым вы можете сделать это для вашего удобства @LuminosityXVII
4. Хм. Это полезно, но я не уверен, следует ли мне принимать это как ответ, поскольку это скорее альтернатива, чем определенное улучшение, и я более склонен использовать свою версию в данном случае. Я протестирую
np.where
и воспользуюсь им, если это будет быстрее.5. …Хорошо, в конце концов, я принимаю это, в основном для np.where, но также и для понимания слияния. Спасибо, вы были полезны.