【问题标题】:How to group consecutive NaN values from a Pandas Series in a set of slices?如何将 Pandas 系列中的连续 NaN 值分组到一组切片中?
【发布时间】:2019-10-09 22:23:39
【问题描述】:

我想将连续的 NaN 值合并到切片中。有没有使用 numpy 或 pandas 的简单方法?

l = [
    (996, np.nan), (997, np.nan), (998, np.nan),
    (999, -47.3), (1000, -72.5), (1100, -97.7),
    (1200, np.nan), (1201, np.nan), (1205, -97.8),
    (1300, np.nan), (1302, np.nan), (1305, -97.9),
    (1400, np.nan), (1405, -97.10), (1408, np.nan)
]
l = pd.Series(dict(l))

预期结果:

[
    (slice(996, 999, None), array([nan, nan, nan])),
    (999, -47.3),
    (1000, -72.5),
    (1100, -97.7),
    (slice(1200, 1202, None), array([nan, nan])),
    (1205, -97.8),
    (slice(1300, 1301, None), array([nan])),
    (slice(1302, 1303, None), array([nan])),
    (1305, -97.9),
    (slice(1400, 1401, None), array([nan])),
    (1405, -97.1),
    (slice(1408, 1409, None), array([nan]))
]

具有二维的 numpy 数组也可以,而不是元组列表

2019/05/31 更新:我刚刚意识到,如果我只使用字典而不是 Pandas 系列,算法效率会更高

【问题讨论】:

    标签: python python-3.x pandas numpy nan


    【解决方案1】:

    你想要的是完整或极端情况,nan 相等,每对的第一个元素是切片或单个值,第二个是 np.array 或单个值。

    对于如此复杂的需求,我只会依赖普通的 Python 非矢量化方式:

    def trans(ser):
        def build(last, cur, val):
            if cur == last + 1:
                if np.isnan(val):
                    return (slice(last, cur), np.array([np.nan]))
                else:
                    return (last, val)
            else:
                return (slice(last, cur), np.array([val] * (cur - last)))
        last = ser.iloc[0]
        old = last_index = ser.index[0]
        resul = []
        for i in ser.index[1:]:
            val = ser[i]
            if ((val != last) and not(np.isnan(val) and np.isnan(last))) \
               or i != old + 1:
                resul.append(build(last_index, old + 1, last))
                last_index = i
                last = val
            old = i
        resul.append(build(last_index, old+1, last))
        return resul
    

    它给出了接近预期结果的东西:

    [(slice(996, 999, None), array([nan, nan, nan])),
     (999, -47.3),
     (1000, -72.5),
     (1100, -97.7),
     (slice(1200, 1202, None), array([nan, nan])),
     (1205, -97.8),
     (slice(1300, 1301, None), array([nan])),
     (slice(1302, 1303, None), array([nan])),
     (1305, -97.9),
     (slice(1400, 1401, None), array([nan])),
     (1405, -97.1),
     (slice(1408, 1409, None), array([nan]))]
    

    【讨论】:

    • 我明白了,谢谢你的建议。正如我在预期结果中所写的那样,索引 1300、1302、1400 和 1402 不应包含在一个巨大的切片中,因为它们是不连续的索引。所以我需要用一种稍微不同的方式来实现它
    • 啊!!我在写示例时犯了一个错误。对不起@Serge。事实上,指数不应重复。我已经更新了我的问题。那么你之前的算法可能是对的
    • 我已经更新了我的原始算法以正确处理非连续索引。
    • 我在您的代码中又添加了一个条件以获得准确的预期结果。此外,NaN 和具有相同数据的单元格也被组合在一起,这很酷。谢谢!!
    • 我刚刚意识到,如果我只使用字典而不是 Pandas 系列,那么算法会更有效
    【解决方案2】:

    Group by cumsum of notnull 是个好主意,但是我们需要过滤掉每个子系列中的第一个非空值,所以我们可以通过 (cumsum, notnull) 对进行分组:

    # convert series to frame, 
    # don't know why series only doesn't work
    df = l.to_frame(name='val')
    
    df['notnull'] = df['val'].notnull()
    g = df.groupby([ df['notnull'].cumsum(), 'notnull']).val
    
    [(v.index, v.values) for i, v in g]
    

    输出:

    [(Int64Index([996, 997, 998], dtype='int64'), array([nan, nan, nan])),
     (Int64Index([1200, 1201], dtype='int64'), array([nan, nan])),
     (Int64Index([1300, 1302, 1400, 1402], dtype='int64'),
      array([nan, nan, nan, nan])),
     (Int64Index([999], dtype='int64'), array([-47.3])),
     (Int64Index([1000], dtype='int64'), array([-72.5])),
     (Int64Index([1100], dtype='int64'), array([-97.7])),
     (Int64Index([1202], dtype='int64'), array([-97.1]))]
    

    编辑:考虑连续索引并更新切片:

    # convert group to slices
    def get_slice(x):
        idx_min, idx_max = x.index.min(), x.index.max()
    
        if len(x) >1:
            return (slice(idx_min, idx_max+1), x.values)
        elif x.isna().any():
            return (slice(idx_min, idx_min+1), x.values)
        else:
            return (idx_min, x[idx_min])
    
    df['notnull'] = df['val'].notnull()
    
    # non-continuous indices
    df['sep'] = (df.index != df.index.to_series().shift() + 1).cumsum()
    
    g = df.groupby(['sep', df['notnull'].cumsum(), 'notnull']).val
    
    g.apply(get_slice).values.tolist()
    

    给予:

    [(slice(996, 999, None), array([nan, nan, nan])),
     (999, -47.3),
     (1000, -72.5),
     (1100, -97.7),
     (slice(1200, 1202, None), array([nan, nan])),
     (1205, -97.8),
     (slice(1300, 1301, None), array([nan])),
     (slice(1302, 1303, None), array([nan])),
     (1305, -97.9),
     (slice(1400, 1401, None), array([nan])),
     (1405, -97.1),
     (slice(1408, 1409, None), array([nan]))]
    

    【讨论】:

    • 您的解决方案看起来很优雅,但我需要最后一步将数组位置转换为切片,如果它们不连续,则制作单独的元素。正如 Serge 在他的回答中所说,恐怕没有矢量化的方式来实现它
    • 感谢编辑,现在结果是正确的。我一直在分析完成处理需要多长时间,而 Serge 算法需要的时间要少得多(405 µs ± 22.2 µs 超过 14.5 ms ± 310 µs)。所以我会坚持他的版本。您认为从长远来看,您的算法会更有效吗?
    • 无论如何我都赞成你的答案,因为它很有用
    猜你喜欢
    • 2014-11-25
    • 1970-01-01
    • 2018-07-04
    • 2020-04-02
    • 1970-01-01
    • 2021-09-12
    • 1970-01-01
    • 1970-01-01
    • 2015-06-06
    相关资源
    最近更新 更多