如何将连续的NaN值从Pandas Series中分组成一组切片?

7

我希望将连续的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系列,算法会更加高效。

2个回答

2
你需要的是全面或边缘情况、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不应包含在一个巨大的切片中,因为它们是非连续的索引。因此,我需要以稍微不同的方式进行处理。 - ChesuCR
啊!!我在写例子的时候犯了一个错误。对不起@Serge。实际上,索引不应该重复。我已经更新了我的问题。那么你之前的算法可能是正确的。 - ChesuCR
我已经更新了原始算法,以正确处理非连续索引。 - Serge Ballesta
我已经在您的代码中添加了一个条件,以获得精确的预期结果。此外,NaN和具有相同数据的单元格也被分组在一起,这很酷。谢谢!! - ChesuCR
我刚刚意识到,如果我使用字典而不是Pandas Series,算法会更加高效。 - ChesuCR

1

按照 cumsumnotnull 分组是个好主意,但我们需要过滤掉每个子序列中的第一个非空值,这样我们就可以按照 (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在他的答案中所说的那样。 - ChesuCR
谢谢您的编辑,结果现在是正确的。我一直在分析处理所需的时间长度,Serge算法需要的时间要少得多(405微秒±22.2微秒超过14.5毫秒±310微秒)。因此,我将坚持使用他的版本。你认为你的算法长远来看会更有效吗? - ChesuCR
无论如何,我已经点赞了你的回答,因为它很有用。 - ChesuCR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接