pandas 如何找到最大外部时间跨度并追加?

xt0899hw  于 2023-03-16  发布在  其他
关注(0)|答案(2)|浏览(135)

bounty将在5天后过期。回答此问题可获得+50声望奖励。dspractician希望引起更多人关注此问题。

我想为每个ID找到00:00:00之前的最早测量时间和00:00:00之后的最晚测量时间。这样我就可以选择最大重叠开始和结束时间。下面是示例数据:

import pandas as pd

# create sample dataframe
df = pd.DataFrame({'ID': [1,1,1,
                          2,2,2,
                          3,3,3],
    'timestamp': ['2022-01-01 12:00:00', '2022-01-01 12:30:00', '2022-01-01 13:00:00',
                  '2022-01-01 12:02:00', '2022-01-01 12:22:00', '2022-01-01 12:42:00',
                  '2022-01-01 12:45:00', '2022-01-01 12:55:00', '2022-01-01 13:05:00'],
    'VALUE1': [50, 80, 65,
               61,83,63,
               64,85,66],
    'gender': ['m', 'm', 'm',
               'f', 'f', 'f',
               'm', 'm', 'm',],
    'age': [7,7,7,
               8,8,8,
               6,6,6, ]
})

我的期望是在图片中找到T_start和T_end(同一天24小时测量)。然后,追加(如虚线)最后一次或第一次测量的相同值。请参考图纸,因为我对问题的措辞可能会混淆:x1c 0d1x
我拥有的:
| 识别号|值1|时间戳|性别|年龄|
| - ------|- ------|- ------|- ------|- ------|
| 1个|五十|2022年1月1日12时00分|米|七|
| 1个|八十|2022年1月1日12时30分|米|七|
| 1个|六十五|2022年1月1日13时00分|米|七|
| 第二章|六十一|2022年1月1日12:02:00|f级|八个|
| 第二章|八十三|2022年1月1日12时22分|f级|八个|
| 第二章|六十三|2022年1月1日12时42分|f级|八个|
| 三个|六十四|2022年1月1日12时45分|米|六个|
| 三个|八十五|2022年1月1日12时55分|米|六个|
| 三个|六十六|2022年1月1日13时05分|米|六个|
我的期望:
| 识别号|值1|时间戳|性别|年龄|
| - ------|- ------|- ------|- ------|- ------|
| 1个|五十|2022年1月1日12时00分|米|七|
| 1个|八十|2022年1月1日12时30分|米|七|
| 1个|六十五|2022年1月1日13时00分|米|七|
| 1个|上一个条目的值=65|2022年1月1日13时05分|米|七|
| 第二章|下一个条目的值=61|2022年1月1日12时00分|f级|八个|
| 第二章|六十一|2022年1月1日12:02:00|f级|八个|
| 第二章|八十三|2022年1月1日12时22分|f级|八个|
| 第二章|六十三|2022年1月1日12时42分|f级|八个|
| 第二章|上一个条目的值|2022年1月1日12时45分|f级|八个|
| 第二章|上一个条目的值|2022年1月1日12时55分|f级|八个|
| 第二章|上一个条目的值|2022年1月1日13时00分|f级|八个|
| 第二章|上一个条目的值|2022年1月1日13时05分|f级|八个|
| 三个|下一个条目的值|2022年1月1日12时00分|米|六个|
| 三个|下一个条目的值|2022年1月1日12:02:00|米|六个|
| 三个|下一个条目的值|2022年1月1日12时22分|米|六个|
| 三个|下一个条目的值=64|2022年1月1日12时42分|米|六个|
| 三个|六十四|2022年1月1日12时45分|米|六个|
| 三个|八十五|2022年1月1日12时55分|米|六个|
| 三个|六十六|2022年1月1日13时05分|米|六个|

46qrfjad

46qrfjad1#

溶液

下面的代码只使用panda函数而不使用循环来解决这个问题。

# Prepare and reshape DataFrame
df['timestamp'] = pd.to_datetime(df['timestamp'])
df["gender"] = df.gender.replace({'f': 0, 'm': 1})
df = df.pivot_table(index = 'timestamp', columns = 'ID')

# Fill outer missing values
df = df.groupby(level = 1, axis = 1, group_keys = False
        ).apply(lambda x: x.loc[x.first_valid_index():x.last_valid_index()]
                           .reindex(x.index, method = "nearest"))

# Bring back to original shape
df = df.stack(dropna = True).swaplevel(0, 1, axis = 0).sort_index()
df = df.replace({"gender": {0: 'f', 1: 'm'}}).convert_dtypes().reset_index()

这将返回以下DataFrame:

>>> df

    ID           timestamp  VALUE1  age gender
0    1 2022-01-01 12:00:00      50    7      m
1    1 2022-01-01 12:30:00      80    7      m
2    1 2022-01-01 13:00:00      65    7      m
3    1 2022-01-01 13:05:00      65    7      m
4    2 2022-01-01 12:00:00      61    8      f
5    2 2022-01-01 12:02:00      61    8      f
6    2 2022-01-01 12:22:00      83    8      f
7    2 2022-01-01 12:42:00      63    8      f
8    2 2022-01-01 12:45:00      63    8      f
9    2 2022-01-01 12:55:00      63    8      f
10   2 2022-01-01 13:00:00      63    8      f
11   2 2022-01-01 13:05:00      63    8      f
12   3 2022-01-01 12:00:00      64    6      m
13   3 2022-01-01 12:02:00      64    6      m
14   3 2022-01-01 12:22:00      64    6      m
15   3 2022-01-01 12:30:00      64    6      m
16   3 2022-01-01 12:42:00      64    6      m
17   3 2022-01-01 12:45:00      64    6      m
18   3 2022-01-01 12:55:00      85    6      m
19   3 2022-01-01 13:05:00      66    6      m

如果不仅外部缺失值而且所有缺失值都应该用相邻值填充,则df.groupby()构造可以用df = df.ffill().bfill()替换。
在一个较旧的解决方案中,在下面的详细答案中,有一个针对df.groupby()对象的循环,而不是df.groupby().apply()

# Fill outer missing values & drop inner missing values
for gid, gdf in df.groupby(level = 1, axis = 1):
    tmin, tmax = gdf.dropna().index[[0, -1]] # get first & last index
    mask = gdf.index.map(lambda x: tmin <= x <= tmax and x not in gdf.dropna().index)
    df.loc[:, pd.IndexSlice[:, gid]] = gdf[~mask].ffill().bfill()

长答案

import pandas as pd
import seaborn as sns

# Convert timestamp to datetime
df['timestamp'] = pd.to_datetime(df['timestamp'])

# replace f/m with 0/1 (because pivot_table cannot aggregate type object)
df["gender"] = df.gender.replace({'f': 0, 'm': 1})

# Pivot table with timestamp as index
df = df.pivot_table(index = 'timestamp', columns = 'ID')

数据框被透视,列是MultiIndex(以“ID”作为第2级):

>>> print("Pivoted DataFrame:", df, sep = "\n")

Pivoted DataFrame:
                    VALUE1              age           gender          
ID                       1     2     3    1    2    3      1    2    3
timestamp                                                             
2022-01-01 12:00:00   50.0   NaN   NaN  7.0  NaN  NaN    1.0  NaN  NaN
2022-01-01 12:02:00    NaN  61.0   NaN  NaN  8.0  NaN    NaN  0.0  NaN
2022-01-01 12:22:00    NaN  83.0   NaN  NaN  8.0  NaN    NaN  0.0  NaN
2022-01-01 12:30:00   80.0   NaN   NaN  7.0  NaN  NaN    1.0  NaN  NaN
2022-01-01 12:42:00    NaN  63.0   NaN  NaN  8.0  NaN    NaN  0.0  NaN
2022-01-01 12:45:00    NaN   NaN  64.0  NaN  NaN  6.0    NaN  NaN  1.0
2022-01-01 12:55:00    NaN   NaN  85.0  NaN  NaN  6.0    NaN  NaN  1.0
2022-01-01 13:00:00   65.0   NaN   NaN  7.0  NaN  NaN    1.0  NaN  NaN
2022-01-01 13:05:00    NaN   NaN  66.0  NaN  NaN  6.0    NaN  NaN  1.0

如果我们想要填充所有缺失值而不仅仅是外部缺失值,这将允许轻松地interpolatefill缺失值:

df = df.ffill().bfill()

为了只填充外部时间跨度中的缺失值,我们还需要几行

# Iterate over IDs (which are the 2nd level)
for gid, gdf in df.groupby(level = 1, axis = 1):
    # Get first and last non-NaN timestamp. Sort in case the index is not sorted.
    tmin, tmax = gdf.dropna().index.sort_values()[[0, -1]]
    # Creating a boolean mask which can be negated with "~"
    mask = gdf.index.map(lambda x: tmin <= x <= tmax and x not in gdf.VALUE1.dropna().index)
    # Fill missing values with the neighboring values & drop masked values
    df.loc[:, pd.IndexSlice[:, gid]] = gdf[~mask].ffill().bfill()

现在我们唯一需要做的就是将DataFrame恢复到其原始形状:

df = df.stack().swaplevel(0, 1, axis = 0).sort_index(axis = 0).astype(int)
df = df.replace({"gender": {0: 'f', 1: 'm'}}).astype({"gender": "category"})

这给了我们最终的DataFrame,其中时间戳是第二层:

>>> print("MultiIndex DataFrame:", df, sep = "\n")

MultiIndex DataFrame:
                        VALUE1  age gender
ID timestamp                              
1  2022-01-01 12:00:00      50    7      m
   2022-01-01 12:30:00      80    7      m
   2022-01-01 13:00:00      65    7      m
   2022-01-01 13:05:00      65    7      m
2  2022-01-01 12:00:00      61    8      f
   2022-01-01 12:02:00      61    8      f
   2022-01-01 12:22:00      83    8      f
   2022-01-01 12:42:00      63    8      f
   2022-01-01 12:45:00      63    8      f
   2022-01-01 12:55:00      63    8      f
   2022-01-01 13:00:00      63    8      f
   2022-01-01 13:05:00      63    8      f
3  2022-01-01 12:00:00      64    6      m
   2022-01-01 12:02:00      64    6      m
   2022-01-01 12:22:00      64    6      m
   2022-01-01 12:30:00      64    6      m
   2022-01-01 12:42:00      64    6      m
   2022-01-01 12:45:00      64    6      m
   2022-01-01 12:55:00      85    6      m
   2022-01-01 13:05:00      66    6      m

要摆脱MultiIndex,我们可以使用reset_index()

df = df.reset_index()

为了绘制数据,我们可以使用Pandas内置的绘图功能或seaborn,这通常会产生稍微更好的绘图:

# df.VALUE1.unstack().T.plot(marker = "o", linestyle = "-")
sns.lineplot(data = df, x = "timestamp", y = "VALUE1",
             hue = "ID", style = "gender", palette = "tab10")

l7wslrjt

l7wslrjt2#

下面是使用Pandas Timestamp执行此操作的一种方法:

# Format values
df["timestamp"] = pd.to_datetime(df["timestamp"], infer_datetime_format=True)

# Iterate on timestamps to find missing ones for each subdataframes per age
dfs = []
for age in df["age"].unique():
    tmp = df.loc[df["age"] == age, :].reset_index(drop=True)
    min_timestamp = tmp["timestamp"].min()
    max_timestamp = tmp["timestamp"].max()
    for timestamp in df["timestamp"].unique():
        if (
            pd.Timestamp(timestamp) < min_timestamp
            or pd.Timestamp(timestamp) > max_timestamp
        ):
            tmp.loc[tmp.shape[0], :] = [pd.NA, timestamp, pd.NA, pd.NA, pd.NA]
    dfs.append(
        tmp.sort_values("timestamp").fillna(method="bfill").fillna(method="ffill")
    )

# Concatenate sub dataframes into one
new_df = pd.concat(dfs, ignore_index=True).astype({"VALUE1": int, "age": int})

然后:

print(new_df)
# Output
     ID           timestamp  VALUE1 gender  age
0   1.0 2022-01-01 12:00:00      50      m    7
1   1.0 2022-01-01 12:30:00      80      m    7
2   1.0 2022-01-01 13:00:00      65      m    7
3   1.0 2022-01-01 13:05:00      65      m    7
4   2.0 2022-01-01 12:00:00      61      f    8
5   2.0 2022-01-01 12:02:00      61      f    8
6   2.0 2022-01-01 12:22:00      83      f    8
7   2.0 2022-01-01 12:42:00      63      f    8
8   2.0 2022-01-01 12:45:00      63      f    8
9   2.0 2022-01-01 12:55:00      63      f    8
10  2.0 2022-01-01 13:00:00      63      f    8
11  2.0 2022-01-01 13:05:00      63      f    8
12  3.0 2022-01-01 12:00:00      64      m    6
13  3.0 2022-01-01 12:02:00      64      m    6
14  3.0 2022-01-01 12:22:00      64      m    6
15  3.0 2022-01-01 12:30:00      64      m    6
16  3.0 2022-01-01 12:42:00      64      m    6
17  3.0 2022-01-01 12:45:00      64      m    6
18  3.0 2022-01-01 12:55:00      85      m    6
19  3.0 2022-01-01 13:05:00      66      m    6

相关问题