#python #pandas
Вопрос:
У меня проблема с временными рядами, и я хочу объединить некоторые данные на основе значений, которые отображаются в определенном столбце. для иллюстрации рассмотрим следующую таблицу
Дата | кола | Кольб | Кольк |
---|---|---|---|
2019-01-01 | 1 | -10 | Нулевой |
2019-01-02 | 2 | -5 | Нулевой |
2019-01-03 | 3 | 0 | 101 |
2019-01-04 | 4 | 5 | 101 |
2019-01-05 | 5 | 10 | 101 |
2019-01-06 | 6 | 15 | Нулевой |
2019-01-07 | 7 | 20 | 101 |
Я хочу выполнить следующее:
- в тот момент, когда значение ColC не равно null, агрегируйте значения до этой строки и получите дельту для столбца даты
- если элемент X в ColC не равен нулю, но элемент (X-1) также не равен нулю, просто проигнорируйте строку X.
Для предыдущей таблицы результатом будет
agg(ColC) | авг(колА) | avg(ColB) | дельта(Дата) [в днях] |
---|---|---|---|
101 | 2 | -5 | 2 |
101 | 6.5 | 17.5 | 1 |
До сих пор я не мог найти никакого способа добиться этого
Ответ №1:
Попробуйте с groupby
:
#convert Date column to datetime if needed
df["Date"] = pd.to_datetime(df["Date"])
#keep only rows where there aren't consecutive non-null values
df2 = df[~(df["colC"].notnull()amp;df["colC"].shift().notnull())]
#groupby consecutive null values and aggregate
output = df2.groupby(df2["colC"].notnull().shift().cumsum().fillna(0))
.agg({"colA": "mean",
"colB": "mean",
"colC": "first",
"Date": lambda x: (x.max()-x.min()).days})
.rename_axis(None)
.rename(columns={"Date": "Delta"})
>>> output
colA colB colC Delta
0.0 2.0 -5.0 101.0 2
1.0 6.5 17.5 101.0 1
Ответ №2:
Вы можете установить группы colC
с cumsum()
, а затем сгруппировать по группам по .groupby()
следующим образом:
(При условии, что нулевые значения равны NaN
или отсутствуют):
# Convert date to datetime format
df['Date'] = pd.to_datetime(df['Date'])
# disregard rows if the element X of colC is not null but the element (X-1) is also not null
df2 = df.loc[df['colC'].isna() | df['colC'].shift().isna()]
# set grouping for `colC`
group = (df2['colC'].shift(1).notna() amp; df2['colC'].isna()).cumsum()
df_out = (df2.groupby(group, as_index=False)
.agg(**{'agg(colC)':('colC', 'last'),
'avg(colA)':('colA', 'mean'),
'avg(colB)':('colB', 'mean'),
'delta(Date)':('Date', lambda x: (x.iloc[-1] - x.iloc[0]).days)})
)
Ввод данных:
data = {'Date': ['2019-01-01', '2019-01-02', '2019-01-03', '2019-01-04', '2019-01-05', '2019-01-06', '2019-01-07'],
'colA': [1, 2, 3, 4, 5, 6, 7],
'colB': [-10, -5, 0, 5, 10, 15, 20],
'colC': [np.nan, np.nan, 101.0, 101.0, 101.0, np.nan, 101.0]}
df = pd.DataFrame(data)
Date colA colB colC
0 2019-01-01 1 -10 NaN
1 2019-01-02 2 -5 NaN
2 2019-01-03 3 0 101.0
3 2019-01-04 4 5 101.0
4 2019-01-05 5 10 101.0
5 2019-01-06 6 15 NaN
6 2019-01-07 7 20 101.0
Результат:
print(df_out)
agg(colC) avg(colA) avg(colB) delta(Date)
0 101.0 2.0 -5.0 2
1 101.0 6.5 17.5 1
Объяснение / Иллюстрация
Давайте добавим больше данных для лучшей иллюстрации:
data = {'Date': ['2019-01-01', '2019-01-02', '2019-01-03', '2019-01-04', '2019-01-05', '2019-01-06', '2019-01-07', '2019-01-08', '2019-01-09', '2019-01-10', '2019-01-11', '2019-01-12', '2019-01-13', '2019-01-14', '2019-01-15'],
'colA': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
'colB': [-10, -5, 0, 5, 10, 15, 15, 15, 20, 20, 15, 15, 15, 20, 20],
'colC': [np.nan, np.nan, 101, 101, 101, np.nan, np.nan, 102, 103, 104, np.nan, np.nan, np.nan, 112, 113]}
df = pd.DataFrame(data)
Date colA colB colC
0 2019-01-01 1 -10 NaN
1 2019-01-02 2 -5 NaN
2 2019-01-03 3 0 101.0
3 2019-01-04 4 5 101.0
4 2019-01-05 5 10 101.0
5 2019-01-06 6 15 NaN
6 2019-01-07 7 15 NaN
7 2019-01-08 8 15 102.0
8 2019-01-09 9 20 103.0
9 2019-01-10 10 20 104.0
10 2019-01-11 11 15 NaN
11 2019-01-12 12 15 NaN
12 2019-01-13 13 15 NaN
13 2019-01-14 14 20 112.0
14 2019-01-15 15 20 113.0
После преобразования формата даты мы удаляем ненужные строки, сохраняя только строки, которые либо NaN
включены colC
, либо даже если текущая строка не NaN
включена, но ее предыдущая строка NaN
включена colC
.
# Convert date to datetime format
df['Date'] = pd.to_datetime(df['Date'])
df2 = df.loc[df['colC'].isna() | df['colC'].shift().isna()]
Результат:
print(df2)
Date colA colB colC
0 2019-01-01 1 -10 NaN
1 2019-01-02 2 -5 NaN
2 2019-01-03 3 0 101.0
5 2019-01-06 6 15 NaN
6 2019-01-07 7 15 NaN
7 2019-01-08 8 15 102.0
10 2019-01-11 11 15 NaN
11 2019-01-12 12 15 NaN
12 2019-01-13 13 15 NaN
13 2019-01-14 14 20 112.0
Теперь, для настройки групп, для иллюстрации, давайте покажем значения промежуточных столбцов также в фрейме данных, создав промежуточные столбцы:
df2['Group_indicator'] = df2['colC'].shift(1).notna() amp; df2['colC'].isna()
Мы устанавливаем логическую маску так, чтобы для первой записи группы было установлено True
значение, а для других записей False
(в первой строке есть исключение, но нет проблем, когда мы генерируем серийные номера для групп)
Результат:
Date colA colB colC Group_indicator
0 2019-01-01 1 -10 NaN False
1 2019-01-02 2 -5 NaN False
2 2019-01-03 3 0 101.0 False
5 2019-01-06 6 15 NaN True
6 2019-01-07 7 15 NaN False
7 2019-01-08 8 15 102.0 False
10 2019-01-11 11 15 NaN True
11 2019-01-12 12 15 NaN False
12 2019-01-13 13 15 NaN False
13 2019-01-14 14 20 112.0 False
Затем мы генерируем номера групп:
df2['Group'] = df2['Group_indicator'].cumsum()
Результат:
Date colA colB colC Group_indicator Group
0 2019-01-01 1 -10 NaN False 0
1 2019-01-02 2 -5 NaN False 0
2 2019-01-03 3 0 101.0 False 0
5 2019-01-06 6 15 NaN True 1
6 2019-01-07 7 15 NaN False 1
7 2019-01-08 8 15 102.0 False 1
10 2019-01-11 11 15 NaN True 2
11 2019-01-12 12 15 NaN False 2
12 2019-01-13 13 15 NaN False 2
13 2019-01-14 14 20 112.0 False 2
Вы можете видеть, что серийные номера групп генерируются для каждой группы с одинаковыми номерами групп для всех записей одной и той же группы.
После этого мы объединяемся в группу с этим номером группы и объединяем строки с кодом:
df_out = (df2.groupby('Group', as_index=False)
.agg(**{'agg(colC)':('colC', 'last'),
'avg(colA)':('colA', 'mean'),
'avg(colB)':('colB', 'mean'),
'delta(Date)':('Date', lambda x: (x.iloc[-1] - x.iloc[0]).days)})
)
Здесь, поскольку наша заинтересованная запись colC
является последней записью в группе, мы объединяемся с 'last'
on colC
.
Date
Чтобы получить агрегацию delta(Date)
, мы берем последнюю/первую запись Date
с группой по x.iloc[-1]
/ x.iloc[0]
(т. Е. последнюю и первую записи серии Pandas для Date
внутри группы) и вычисляем их разницу путем вычитания. Поскольку это временной ряд, последние/первые записи должны соответствовать записям max/min Date
внутри группы.
Следовательно, результат:
Group agg(colC) avg(colA) avg(colB) delta(Date)
0 0 101.0 2.0 -5.00 2
1 1 102.0 7.0 15.00 2
2 2 112.0 12.5 16.25 3
Здесь результат содержит столбец Group
, в то время как наша исходная версия кода без него. Это связано с тем, что мы определили отдельную серию Панд group
для группировки, а не определили вспомогательный столбец Group
на этом рисунке.