【问题标题】:Indices of parts of data containing null values包含空值的部分数据的索引
【发布时间】:2020-07-29 14:46:11
【问题描述】:

我正在寻找一种算法,它允许我搜索和获取系列中所有间隙(nans)的索引,其中索引是指“分区”的开始和结束。我找不到解决方案,所以我最终使用了自己创建的代码。一切都很好,除了这两种方法似乎有点慢。我想知道是否有办法优化代码。

我尝试了两种方法。第一个是对所有索引进行简单的 for 循环并检查是否继续。另一个删除 nan 值,然后再次使用 List Comprehension 检查是否继续。后一种方法更快。

我想知道是否有更好的方法来提高速度,或者我错过了一些已经内置的东西。谢谢。


数据:

import numpy as np
import pandas as pd

# Create an object with sample data
w = pd.Series(np.sin(2*np.pi*np.linspace(0,1,2880)))
# Insert a few gaps with missing values
for i in np.arange(0, 1500, 200):
    w.loc[w.index[0]+i:w.index[0]+i+100] = np.nan
w.loc[2880-100:] = np.nan```


第一种方法:

# Get indices
# `l_nans` stores the first and the last index of each gap
t0 = time()
for c in range(1000):
    i_nans = w[w.isnull()].index.to_numpy()
    len_nans = i_nans.shape[0]
    f, l, p, n = np.nan, np.nan, np.nan, np.nan
    l_nans = list()
    i = 0
    for i, e in enumerate(i_nans.tolist()):
        if not np.isnan(n):
            p = n
        n = e
        if np.isnan(f):
            f = e
        if (n-p) > 1:
            l = p
            l_nans.append((f, l))
            f, l = e, np.nan
        if i == len_nans-1:
            l = n
            l_nans.append((f, l))
print(l_nans)
print(time() - t0)

[(0, 100), (200, 300), (400, 500), (600, 700), (800, 900), (1000, 1100), (1200, 1300), (1400, 1500), (2780, 2879)]
3.1106319427490234


第二种方法:

# Get indices
# `l_nans` stores the first and the last index of each gap
t0 = time()
for c in range(1000):
    v = w.drop(w[w.isnull()].index, axis=0)
    l_nans = [(e[0]+1, e[1]-1) for e in zip(v.index[:-1], v.index[1:]) if e[1]-e[0] > 1]
    if not any(v.index.isin([w.index[0]])):
        l_nans.insert(0, (0, v.first_valid_index()-1))
    if not any(v.index.isin([w.index[-1]])):
        l_nans.append((v.last_valid_index()+1, w.index[-1]))
print(l_nans)
print(time() - t0)

[(0, 100), (200, 300), (400, 500), (600, 700), (800, 900), (1000, 1100), (1200, 1300), (1400, 1500), (2780, 2879)]
1.8505527973175049

编辑。

我意识到我的真​​实数据的某些部分具有单个 nan 值。所以示例数据如下:

import numpy as np
import pandas as pd

# Create an object with sample data
w = pd.Series(np.sin(2*np.pi*np.linspace(0,1,2880)))
# Insert a few gaps with missing values
for i in np.arange(0, 1500, 200):
    w.loc[w.index[0]+i:w.index[0]+i+100] = np.nan
w.loc[2880-100:] = np.nan
w.loc[1600] = np.nan
w.loc[1700] = np.nan

【问题讨论】:

  • 你能贴几行df测试数据和想要的结果吗?
  • @ZarakiKenpachi w 这里是测试数据,想要的结果是每个方法下面的元组列表。
  • 我稍微改变了问题并添加了一些 cmets 以提高清晰度。 wpandasSeries 对象)包含带有NaNs 的样本数据,l_nans 是一个列表对象,用于存储每个间隙的所有范围索引。

标签: python pandas nan


【解决方案1】:

您可以使用来自https://www.geeksforgeeks.org/python-make-a-list-of-intervals-with-sequential-numbers/intervals_extract 配方

import itertools 
  
def intervals_extract(iterable): 
      
    iterable = sorted(set(iterable)) 
    for key, group in itertools.groupby(enumerate(iterable), 
    lambda t: t[1] - t[0]): 
        group = list(group) 
        yield [group[0][1], group[-1][1]] 

itertools.groupby 将数据组合在一起只要键函数返回相同的值。关键函数是两个连续值之间的差,只要它们属于相同的区间,则为 1,否则为更大的值。这也是我们使用集合并对其进行排序:避免重复或错误排序的值。因此我们得到了每个区间的迭代器(group)。唯一剩下的就是使用 list 函数使用迭代器并产生每个的第一个和最后一个值。对于这种情况,直接打印值会更简单一些,但像这样它保持更通用。

只需使用您拥有NaNs 的索引作为输入:

In [72]: list(intervals_extract(w[w.isna()].index))                                                                                                                                                        
Out[72]: 
[[0, 100],
 [200, 300],
 [400, 500],
 [600, 700],
 [800, 900],
 [1000, 1100],
 [1200, 1300],
 [1400, 1500],
 [1600, 1600],
 [1700, 1700],
 [2780, 2879]]
In [73]: %timeit list(intervals_extract(w[w.isna()].index))                                                                                                                                                
485 µs ± 5.16 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

编辑:解释了 intervals_extract 函数背后的想法

【讨论】:

  • 这看起来很整洁。你能解释一下代表什么群体吗?
  • 它工作正常而且速度很快。我正在尝试了解 groupby 的工作原理,但遇到了一些问题,但谢谢!
【解决方案2】:

这是另一个版本。简而言之,我们找到带有 NaN 的索引值(一行),然后我们找到连续 NaN 的起点和终点。

import numpy as np
import pandas as pd
import time

# Create an object with sample data
w = pd.Series(np.sin(2*np.pi*np.linspace(0,1,2880)))
# Insert a few gaps with missing values
for i in np.arange(0, 1500, 200):
    w.loc[w.index[0]+i:w.index[0]+i+100] = np.nan
w.loc[2880-100:] = np.nan

大部分代码都是打印语句:

start_time = time.time()

# find index such that w is NaN
idx = w[ w.isna() ].index

# find the break-points
# idx[1:] is the index (except the first value)
# idx[:-1] is the index (except the last value)
# this allows us to calculate distance from current to previous

print(f'[({idx[0]}, ', end='')

for curr, prev in zip(idx[1:], idx[:-1]):
    diff = curr - prev
    if diff > 1:
        print(f'{prev}),')
        print(f'({curr}, ', end='')
print(f'{idx[-1]})]')

end_time = time.time()
print('time = %f' % (end_time-start_time))

[(0, 100),
(200, 300),
(400, 500),
(600, 700),
(800, 900),
(1000, 1100),
(1200, 1300),
(1400, 1500),
(2780, 2879)]
time = 0.002937

【讨论】:

    【解决方案3】:

    只是循环的更优化版本:

    w2 = w.index[w.isna()].tolist()
    s = e = w2[0]
    l_nans = []
    for i in range(1, len(w2)):
        if w2[i] != 1 + e:
            l_nans.append((s, e))
            s = w2[i]
        e = w2[i]
    if e - s >= 1:
        l_nans.append((s, e))
    

    输出:

    [(0, 100),
     (200, 300),
     (400, 500),
     (600, 700),
     (800, 900),
     (1000, 1100),
     (1200, 1300),
     (1400, 1500),
     (2780, 2879)]
    

    性能(%%timeit):

    392 µs ± 9.94 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    

    您有一个开始和结束指针,se。只需将ind[i] 分配给e(e 将始终等于您检查的最后一个元素),if ind[i] - e > 1 表示您移动到另一个范围,因此添加范围并将s 设置为ind[i],然后重复直到结束。

    最后,由于循环可能在到达 e - s > 1 之前就中断了,所以检查最后一个索引 - 开始是否大于 1,那么这意味着最后一个索引形成了一个范围,所以将其添加到列表中.

    【讨论】:

      【解决方案4】:

      你可以让这个循环更快。

      import pandas as pd
      import numpy as np
      import time
      
      df = pd.Series(np.sin(2*np.pi*np.linspace(0,1,2880)))
      for i in np.arange(0, 1500, 200):
          df.loc[df.index[0]+i:df.index[0]+i+100] = np.nan
      df.loc[2880-100:] = np.nan
      
      
      start_time = time.time()
      data = df.index[df.isnull() == True].tolist() + [10**6]
      
      nan_range = []
      start = 0
      for i in range(len(data)-1):
          if data[i] + 2 < data[i+1]:
              end = data[i]
              nan_range.append((start, end))
              start = data[i+1]
      
      end_time = time.time()
      print('time = %f' % (end_time-start_time))
      

      输出:

      [(0, 100), (200, 300), (400, 500), (600, 700), (800, 900), (1000, 1100), (1200, 1300), (1400, 1500), (2780, 2879)]
      
      time = 0.000942
      

      【讨论】:

      • 感谢您的代码。它很快,但不幸的是它没有捕捉到最后的差距!
      • @Celdor 是的,昨天我没有时间完成这个。要更正解决方案,只需将渐变添加到列表中:)。代码更新
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-02-07
      • 1970-01-01
      • 1970-01-01
      • 2019-12-15
      • 1970-01-01
      • 2013-07-07
      • 1970-01-01
      相关资源
      最近更新 更多