Polars - 使用基于过滤集的“三规则”填充空值

2023-12-19

Goal我想通过分配下一个非空值和上一个非空值之间的差异来填充一系列中的空值。分布不是线性的,而是使用另一列中的值来计算分配

Example

df = pl.DataFrame({
    "id": ["a", "a", "a", "b", "b", "b", "b", "b"],
    "timestamp": ["2023-09-13 14:05:34", "2023-09-13 14:15:04", "2023-09-13 14:30:01", "2023-09-13 12:12:02", "2023-09-13 12:15:02", "2023-09-13 12:30:07", "2023-09-13 12:45:01", "2023-09-13 13:00:02"],
    "value": [10, None, 30, 5, 10, None, None, 40]
}).with_columns(
    pl.col("timestamp").str.strptime( pl.Datetime, "%Y-%m-%d %H:%M:%S"),
)

shape: (8, 3)
┌─────┬─────────────────────┬───────┐
│ id  ┆ timestamp           ┆ value │
│ --- ┆ ---                 ┆ ---   │
│ str ┆ datetime[μs]        ┆ i64   │
╞═════╪═════════════════════╪═══════╡
│ a   ┆ 2023-09-13 14:05:34 ┆ 10    │
│ a   ┆ 2023-09-13 14:15:04 ┆ null  │
│ a   ┆ 2023-09-13 14:30:01 ┆ 30    │
│ b   ┆ 2023-09-13 12:12:02 ┆ 5     │
│ b   ┆ 2023-09-13 12:15:02 ┆ 10    │
│ b   ┆ 2023-09-13 12:30:07 ┆ null  │
│ b   ┆ 2023-09-13 12:45:01 ┆ null  │
│ b   ┆ 2023-09-13 13:00:02 ┆ 40    │
└─────┴─────────────────────┴───────┘

预期输出(带有一些中间列来显示其计算方式)

id timestamp value gap value gap time s gap proportion portion fill value final
a 2023/09/13 14:05:34 10 10
a 2023/09/13 14:15:04 null 20 1467 570 7.77 17.77 17.77
a 2023/09/13 14:30:01 30 30
b 2023/09/13 12:12:02 5 5
b 2023/09/13 12:15:02 10 10
b 2023/09/13 12:30:07 null 30 2700 905 10.06 20.06 20.06
b 2023/09/13 12:45:01 null 30 2700 1799 19.99 29.99 29.99
b 2023/09/13 13:00:02 40 40

这个计算是如何进行的
我将以b组为例。

  • 有 2 行包含空值需要填充。
  • 下一个值和上一个值之间的差是 30 ( 40 - 10 )
  • 下一个值与上一个值之间的时间差为 2700 秒 (13:00:02 - 12:15:02)
  • 对于第一个空白行,时间差为 905 秒 (12:30:07 - 12:15:02 )。所以这一行得到分配的部分 30 * ( 905 / 2700 ) (10.06)
  • 所以填充时填充值为 10 + 10.06
  • 下一个空白行得到 30 * ( 1799 / 2700 ) (19.99) 的一部分,所以它的填充值为 10 + 19.99

谢谢您的帮助。我对 Polars 和 Python 都很陌生,所以我的 SQL 头脑仍然围绕着这一切。

就我个人而言,我认为这将是对 fill_null 的一个很好的补充,能够使用三规则,使用不同的列来进行比例

Thanks


(
    df
    .join_asof(
        df
            .filter(pl.col('value').is_not_null())
            .with_columns(
                gap_time=(pl.col('timestamp')-pl.col('timestamp').shift().over('id'))
                            .dt.seconds(),
                prev_good_time=pl.col('timestamp').shift().over('id'),
                prev_good_value=pl.col('value').shift().over('id')
                )
            .drop('value'),
        on='timestamp', by='id', strategy='forward'
        )
    .with_columns(
        gap_value=pl.when(pl.col('value').is_null())
                  .then((pl.col('value')-((pl.col('value')
                                          .forward_fill().shift()
                                          ).over('id'))).backward_fill()),
        gap_time=pl.when(pl.col('value').is_null())
                    .then(pl.col('gap_time')),
        gap_proportion=pl.when(pl.col('value').is_null())
                    .then((pl.col('timestamp')-pl.col('prev_good_time')).dt.seconds()),
                  )
    .with_columns(
        portion=pl.col('gap_value')*(pl.col('gap_proportion')/pl.col('gap_time'))
    )
    .with_columns(
        fill_value=pl.col('prev_good_value')+pl.col('portion')
    )
    .select(
        'id','timestamp',
        value=pl.when(pl.col('value').is_null())
            .then(pl.col('fill_value'))
            .otherwise(
                pl.col('value')
            )
    )
)

我们做的第一件事是对原始版本的过滤版本执行 join_asof 。这使我们能够计算有效值之间的时间,并留出与非空值和值本身关联的最近时间。 join 的 asof 部分表示将加入on基于时间但滚动直到找到下一个(或上一个)匹配时间,然后by其他一些等式列。

您可以嵌套大部分其余的计算,而无需重复自己或使用如此多的上下文,但我让它非常冗长,因此很容易解构。之所以有这么多的电话with_columns的问题是您无法在同一上下文中设置和使用列,因此每当您创建要再次使用的列时,您都必须链接另一个上下文。

输出(不包括中间列)

shape: (8, 3)
┌─────┬─────────────────────┬───────────┐
│ id  ┆ timestamp           ┆ value     │
│ --- ┆ ---                 ┆ ---       │
│ str ┆ datetime[μs]        ┆ f64       │
╞═════╪═════════════════════╪═══════════╡
│ a   ┆ 2023-09-13 14:05:34 ┆ 10.0      │
│ a   ┆ 2023-09-13 14:15:04 ┆ 17.770961 │
│ a   ┆ 2023-09-13 14:30:01 ┆ 30.0      │
│ b   ┆ 2023-09-13 12:12:02 ┆ 5.0       │
│ b   ┆ 2023-09-13 12:15:02 ┆ 10.0      │
│ b   ┆ 2023-09-13 12:30:07 ┆ 20.055556 │
│ b   ┆ 2023-09-13 12:45:01 ┆ 29.988889 │
│ b   ┆ 2023-09-13 13:00:02 ┆ 40.0      │
└─────┴─────────────────────┴───────────┘

numpy 可以做到

这是一种让 numpy 完成这项工作的 hacky(就好像上面的内容不是 hacky 一样)的方法。

finaldf=[]
df=df.with_columns(pl.col('value').cast(pl.Float64))
for little_df in df.partition_by('id'):
    x=little_df.filter(pl.col('value').is_null()).select(pl.col('timestamp').to_physical()).to_numpy()
    xp,fp = little_df.filter(pl.col('value').is_not_null()).select('timestamp','value').to_numpy().transpose()
    finaldf.append(
        pl.concat([
            little_df.filter(pl.col('value').is_not_null()).lazy(),
            little_df.filter(pl.col('value').is_null()).with_columns(value=pl.Series(np.interp(x, xp, fp).transpose()[0])).lazy()
        ])
    )
finaldf=pl.concat(finaldf).sort(['id','timestamp']).collect()
finaldf
shape: (8, 3)
┌─────┬─────────────────────┬───────────┐
│ id  ┆ timestamp           ┆ value     │
│ --- ┆ ---                 ┆ ---       │
│ str ┆ datetime[μs]        ┆ f64       │
╞═════╪═════════════════════╪═══════════╡
│ a   ┆ 2023-09-13 14:05:34 ┆ 10.0      │
│ a   ┆ 2023-09-13 14:15:04 ┆ 17.770961 │
│ a   ┆ 2023-09-13 14:30:01 ┆ 30.0      │
│ b   ┆ 2023-09-13 12:12:02 ┆ 5.0       │
│ b   ┆ 2023-09-13 12:15:02 ┆ 10.0      │
│ b   ┆ 2023-09-13 12:30:07 ┆ 20.055556 │
│ b   ┆ 2023-09-13 12:45:01 ┆ 29.988889 │
│ b   ┆ 2023-09-13 13:00:02 ┆ 40.0      │
└─────┴─────────────────────┴───────────┘

另一种更简洁的极坐标方式

在第一轮中,我专注于复制所有相同的中间列,但如果我直接寻找答案,我们可以做到这一点......

(
    df.join_asof(
    df.filter(pl.col('value').is_not_null())
    .with_columns(
        value_slope=(pl.col('value')-pl.col('value').shift().over('id'))/(pl.col('timestamp')-pl.col('timestamp').shift().over('id')), 
        value_slope_since=pl.col('timestamp').shift(),
        value_base=pl.col('value').shift()
        )
    .drop('value'),
    on='timestamp', by='id', strategy='forward'
    )
    .select('id','timestamp',value=pl.coalesce(pl.col('value'), pl.col('value_base')+pl.col('value_slope')*(pl.col('timestamp')-pl.col('value_slope_since'))))
    )

可扩展的功能

def interp(df, y_col, id_cols=None):
    if not isinstance(y_col, str):
        raise ValueError("y_col should be string")
    if isinstance(id_cols, str):
        id_cols=[id_cols]
    if id_cols is None:
        id_cols=['__dummyid']
        df=df.with_columns(__dummyid=0)
    lf=df.select(id_cols + [y_col]).lazy()
    value_cols=[x for x in df.columns if x not in id_cols and x!=y_col]
    for value_col in value_cols:
        lf=lf.join(
            df.join_asof(
                df.filter(pl.col(value_col).is_not_null())
                .select(
                    *id_cols, y_col,
                    __value_slope=(pl.col(value_col)-pl.col(value_col).shift().over(id_cols))/(pl.col(y_col)-pl.col(y_col).shift().over(id_cols)), 
                    __value_slope_since=pl.col(y_col).shift(),
                    __value_base=pl.col(value_col).shift()
                    ),
                on=y_col, by=id_cols, strategy='forward'
            )
            .select(
                id_cols+ [y_col] + [pl.coalesce(pl.col(value_col), 
                    pl.coalesce(pl.col('__value_base'), pl.col('__value_base').shift(-1))+
                    pl.coalesce(pl.col('__value_slope'), pl.col('__value_slope').shift(-1))*(pl.col(y_col)-
                    pl.coalesce(pl.col('__value_slope_since'), pl.col('__value_slope_since').shift(-1)))).alias(value_col)]
                )
            .lazy(),
            on=[y_col]+id_cols
            )
    if id_cols[0]=='__dummyid':
        lf=lf.select(pl.exclude('__dummyid'))
    return lf.collect()

有了这个功能你就可以做

interp(df, "timestamp", "id")

其中第一个参数是 df,第二个参数是您的时间或 y 列。第三个可选参数是如果您有 id 列(它可以采用列表或单个字符串)。它将推断 df 中未作为时间或 id 列提供给它的任何列都是值,并且它将对它们进行插值。

如果你能将它修补到pl.DataFrame您可以将它用作数据框方法,如下所示

pl.DataFrame.interp=interp
df.interp('timestamp','id')
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Polars - 使用基于过滤集的“三规则”填充空值 的相关文章

随机推荐

  • 从多个中分离出单个

    我需要将图像分割或裁剪为多张图像 下面给出了我的代码 它可以将图像分成 4 块 但我无法使用我的代码创建 6 或 9 块 我是初学者 所以无法找到解决方案 我的代码如下 from scipy import misc Read the ima
  • 非常基本的神经网络不学习

    我已经阅读了一些 CNTK Python 教程 并且正在尝试编写一个非常基本的单层神经网络 可以计算逻辑 AND 我有正常运行的代码 但网络没有学习 事实上 随着每个小批量训练 损失变得越来越严重 import numpy as np fr
  • Cocoapods 声称可以安装但无法运行

    我使用以下命令运行了 cocoapods 的安装sudo gem install cocoapods 该命令声称已成功完成 然而当我这样做时gem list cocoapods它没有显示在列表中 当我这样做时pod version我收到错误
  • 在数据库中保存 celery 任务(用于重新运行)

    我们的工作流程目前是围绕旧版本的 celery 构建的 因此请记住 事情已经不是最佳的 我们需要运行一个任务并将该任务运行的记录保存在数据库中 如果该任务失败或挂起 这种情况经常发生 我们希望重新运行 就像第一次运行一样 但这不应该自动发生
  • 了解何时使用事件以及何时使用回调

    对于事件 发起者引发一个事件 该事件将由那些选择接收该事件的例程接收 接收方指定将从哪些发起方接收哪些事件 通过回调 例程在完成后会通知调用者已完成 因此 我很困惑应该在哪里使用事件或应该在哪里使用回调 因为我可以完成回调对事件的操作 但会
  • 以编程方式确定IP地址是通过dhcp分配还是在Java中手动设置

    有什么方法可以发现本地网络接口的地址是通过 DHCP 分配的还是通过 Java 静态设置的 因此 正如您仅请求 Win NT 解决方案 一样 这是我的代码 它列出了具有当前配置值的网络接口 注意EnableDHCP注册表键值 我想这就是重点
  • 如何使用 TCPDF 与 PHP 邮件功能

    to email protected cdn cgi l email protection subject Receipt repEmail email protected cdn cgi l email protection fileNa
  • 在未初始化的对象上调用方法(空指针)

    如果你在一个为零的对象 指针 上调用一个方法 可能是因为有人忘记初始化它 Objective C 中的正常行为是什么 它不应该产生某种错误 分段错误 空指针异常 吗 如果这是正常行为 是否有办法改变这种行为 通过配置编译器 以便程序在运行时
  • 从解析树中获取某些节点

    我正在研究一个涉及通过霍布斯算法进行照应解析的项目 我已经使用斯坦福解析器解析了我的文本 现在我想操作节点以实现我的算法 目前 我不明白如何 根据 POS 标签访问节点 例如 我需要以代词开头 如何获取所有代词 使用访客 我对 Java 有
  • 相关矩阵的平均值 - pandas dataframe

    我在 pandas python DataFrame 中有一个很大的相关矩阵 df 342 342 如何获取上三角形中所有数字 不包括对角线上的 1 的平均值 标准差等 谢谢 另一个潜在的单行答案 In 1 corr Out 1 a b c
  • 在 Android 中设置音频文件媒体元数据 (ID3)

    我试图找出是否有一种方法可以在 Android 中的 anudio 文件上设置媒体元数据 主要是 ID3 如艺术家 专辑 歌曲等 我注意到可以使用以下方法检索此类信息媒体元数据检索器 http developer android com r
  • 如何为单击一次应用程序指定用户凭据?

    对于常规 exe 文件 我始终可以右键单击并选择 运行为 如何以类似的方式在不同的凭据下运行 Click Once 应用程序 我说的是应用程序本身 而不是安装程序 其实这是可以的 只需要两步就可以了 首先 您需要启动 ClickOnce d
  • MSDN关于存储过程默认返回值

    谁能准确指出 MSDN 所说的如果没有错误发生 每个用户存储过程默认返回 0 的地方吗 换句话说 我可以确定下面给出的示例代码是存储过程吗 IF someStatement BEGIN RETURN 1 END 如果 someStateme
  • Play框架2开发配置

    如何在 Play 2 应用程序中为开发和生产模式设置不同的配置 我尝试在开发模式下的应用程序启动时使用 JVM 参数 如下所示 play run Dconfig resource dev conf 或从 Play 控制台 run Dconf
  • TFS 2015 Build:测试结果未发布

    当 TFS 2015 执行自动化测试步骤时 我遇到了一个非常奇怪的错误 结果已生成 但 TFS 似乎无法找到它们 最后只是说没有找到可以发布的结果 这很奇怪 因为它甚至输出了结果完整路径 检查下面的构建日志以获取更多信息 2015 12 1
  • Edittext 只允许字母(以编程方式)

    我试图获得一个只允许字母 小写和大写 的editTextview 它适用于以下代码 edittv setKeyListener DigitsKeyListener getInstance abcdefghijklmnopqrstuvwxyz
  • Django rss feedparser 返回没有“标题”的提要

    我正在 Django 中编写一个基本的 RSS 提要阅读器 我有一个用户提交 RSS 提要的表单 我将其添加到他的提要列表中 但由于某种原因 我无法使用提要解析器提取有关提要的基本信息 当我运行以下代码时 def form valid se
  • spring 4.3中的注释是什么

    我正在将我的应用程序从 spring 3 x 升级到 spring 4 3 我想要 java 配置 注释 而不是 xml 配置 我无法使用注释进行配置
  • Zend Framework:如何通过指定列的值查找表行?

    我正在实现我的模型 就像快速入门指南 http framework zend com docs quickstart create a model and database table 在我的模型中 我试图实现findByToken 方法
  • Polars - 使用基于过滤集的“三规则”填充空值

    Goal我想通过分配下一个非空值和上一个非空值之间的差异来填充一系列中的空值 分布不是线性的 而是使用另一列中的值来计算分配 Example df pl DataFrame id a a a b b b b b timestamp 2023