【问题标题】:Python: compute average of n-th elements in list of lists with different lengthsPython:计算具有不同长度的列表列表中第 n 个元素的平均值
【发布时间】:2016-07-21 00:32:18
【问题描述】:

假设我有以下列表:

a = [ 
      [1, 2, 3],
      [2, 3, 4],
      [3, 4, 5, 6] 
    ]

我想得到数组中每个第 n 个元素的平均值。但是,当想要以简单的方式执行此操作时,由于长度不同,Python 会产生越界错误。我通过给每个数组提供最长数组的长度并用 None 填充缺失值来解决这个问题。

不幸的是,这样做导致无法计算平均值,因此我将数组转换为掩码数组。下面显示的代码有效,但看起来相当麻烦。

import numpy as np
import numpy.ma as ma

a = [ [1, 2, 3],
      [2, 3, 4],
      [3, 4, 5, 6] ]

# Determine the length of the longest list
lenlist = []
for i in a:
    lenlist.append(len(i))
max = np.amax(lenlist)

# Fill each list up with None's until required length is reached
for i in a:
    if len(i) <= max:
        for j in range(max - len(i)):
            i.append(None)

# Fill temp_array up with the n-th element
# and add it to temp_array 
temp_list = []
masked_arrays = []
for j in range(max):
    for i in range(len(a)):
        temp_list.append(a[i][j])
    masked_arrays.append(ma.masked_values(temp_list, None))
    del temp_list[:]

# Compute the average of each array 
avg_array = []
for i in masked_arrays:
    avg_array.append(np.ma.average(i))

print avg_array

有没有办法更快地做到这一点?列表的最终列表将包含 600000 个“行”和最多 100 个“列”,因此效率非常重要:-)。

【问题讨论】:

    标签: python arrays list numpy nan


    【解决方案1】:

    您也可以避免使用masked array 并改用np.nan

    def replaceNoneTypes(x):
        return tuple(np.nan if isinstance(y, type(None)) else y for y in x)
    
    a = [np.nanmean(replaceNoneTypes(temp_list)) for temp_list in zip_longest(*df[column], fillvalue=np.nan)]
    

    【讨论】:

      【解决方案2】:

      如果您使用 Python 版本 >= 3.4,则导入统计模块

      from statistics import mean
      

      如果使用较低版本,创建一个函数来计算平均值

      def mean(array):
          sum = 0
          if (not(type(array) == list)):
              print("there is some bad format in your input")
          else:
              for elements in array:
                  try:
                      sum = sum + float(elements)
                  except:
                      print("non numerical entry found")
              average = (sum + 0.0) / len(array)
              return average
      

      创建一个列表列表,例如

      myList = [[1,2,3],[4,5,6,7,8],[9,10],[11,12,13,14],[15,16,17,18,19,20,21,22],[23]]
      

      遍历我的列表

      for i, lists in enumerate(myList):
          print(i, mean(lists))
      

      这将打印出序列 n 和第 n 个列表的平均值。

      要特别查找仅第 n 个列表的平均值,请创建一个函数

      def mean_nth(array, n):
          if((type(n) == int) and n >= 1 and type(array) == list):
              return mean(myList[n-1])
          else:
              print("there is some bad format of your input")
      

      请注意,索引从零开始,例如,如果您正在寻找第 5 个列表的平均值,它将位于索引 4。这解释了代码中的 n-1。

      然后调用函数,例如

      avg_5thList = mean_nth(myList, 5)
      print(avg_5thList)
      

      在 myList 上运行上述代码会产生以下结果:

      0 2.0
      1 6.0
      2 9.5
      3 12.5
      4 18.5
      5 23.0
      18.5
      

      其中前六行是从迭代循环生成的,并显示第 n 个列表的索引和列表平均值。最后一行 (18.5) 显示了 mean_nth(myList, 5) 调用后第 5 个列表的平均值。

      此外,对于像您这样的列表,

      a = [ 
            [1, 2, 3],
            [2, 3, 4],
            [3, 4, 5, 6] 
          ]
      

      假设您想要第一个元素的平均值,即 (1+2+3)/3 = 2,或第二个元素,即 (2+3+4)/3 = 3,或第四个元素,例如 6/ 1 = 6,您将需要找到每个列表的长度,以便您可以识别列表中的第 n 个元素是否存在。为此,您首先需要按照列表长度的顺序排列列表。

      你可以

      1) 先根据组成列表的大小对主列表进行迭代排序,然后通过排序后的列表判断组成列表是否足够长

      2) 或者您可以迭代地查看原始列表以了解组成列表的长度。

      (如果需要,我绝对可以重新制定更快的递归算法)

      计算上第二个更有效,因此假设您的第 5 个元素在索引 (0, 1, 2, 3, 4) 中表示第 4 个,或第 n 个元素表示第 (n-1) 个元素,让我们继续吧创建一个函数

      def find_nth_average(array, n):
          if(not(type(n) == int and (int(n) >= 1))):
              return "Bad input format for n"
          else:
              if (not(type(array) == list)):
                  return "Bad input format for main list"
              else:           
                  total = 0
                  count = 0
                  for i, elements in enumerate(array):
                      if(not(type(elements) == list)):
                          return("non list constituent found at location " + str(i+1))                
                      else:
                          listLen = len(elements)
                          if(int(listLen) >= n):
                              try:
                                  total = total + elements[n-1]
                                  count = count + 1
                              except:
                                  return ("non numerical entity found in constituent list " + str(i+1))
                  if(int(count) == 0):
                      return "No such n-element exists"
                  else:
                      average = float(total)/float(count)
                      return average
      

      现在让我们在你的列表中调用这个函数a

      print(find_nth_average(a, 0))
      print(find_nth_average(a, 1))
      print(find_nth_average(a, 2))
      print(find_nth_average(a, 3))
      print(find_nth_average(a, 4))
      print(find_nth_average(a, 5))
      print(find_nth_average(a, 'q'))
      print(find_nth_average(a, 2.3))
      print(find_nth_average(5, 5))
      

      对应的结果是:

      Bad input format for n
      2.0
      3.0
      4.0
      6.0
      No such n-element exists
      Bad input format for n
      Bad input format for n
      Bad input format for main list
      

      如果你有一个不稳定的列表,比如

      a = [[1, 2, 3], 2, [3, 4, 5, 6]]
      

      包含一个非列表元素,你会得到一个输出:

      non list constituent found at location 2 
      

      如果您的成员名单不稳定,例如:

      a = [[1, 'p', 3], [2, 3, 4], [3, 4, 5, 6]]
      

      在列表中包含非数字实体,并通过print(find_nth_average(a, 2))求第二个元素的平均值

      你得到一个输出:

      non numerical entity found in constituent list 1
      

      【讨论】:

      • 我不明白谁对它投了反对票,为什么就这样做而不留下任何评论和推理!!
      • 不是我的,但也许如果你尝试运行你的代码你会明白为什么
      【解决方案3】:

      tertools.izip_longest 将为您完成所有使用 None 的填充,因此您的代码可以简化为:

      import numpy as np
      import numpy.ma as ma
      from itertools import izip_longest
      
      a = [ [1, 2, 3],
            [2, 3, 4],
            [3, 4, 5, 6] ]
      
      
      averages = [np.ma.average(ma.masked_values(temp_list, None)) for temp_list in izip_longest(*a)]
      
      print(averages)
      [2.0, 3.0, 4.0, 6.0]
      

      不知道关于 numpy 逻辑的最快方法是什么,但这肯定会比您自己的代码更有效率。

      如果您想要更快的纯 python 解决方案:

      from itertools import izip_longest, imap
      
      a = [[1, 2, 3],
           [2, 3, 4],
           [3, 4, 5, 6]]
      
      
      def avg(x):
          x = filter(None, x)
          return sum(x, 0.0) / len(x)
      
      
      filt = imap(avg, izip_longest(*a))
      
      print(list(filt))
      [2.0, 3.0, 4.0, 6.0]
      

      如果数组中有 0 将不起作用,因为 0 将被视为 Falsey,在这种情况下您将不得不使用列表组合进行过滤,但它仍然会更快:

      def avg(x):
          x = [i for i in x if i is not None]
          return sum(x, 0.0) / len(x)
      
      filt = imap(avg, izip_longest(*a))
      

      【讨论】:

      • 正是我想要的!非常感谢:)
      • 仅供参考,izip_longest 在 Python 3 中已重命名为 zip_longest
      【解决方案4】:

      这是一个几乎*完全矢量化的解决方案,基于 np.bincountnp.cumsum -

      # Store lengths of each list and their cumulative and entire summations
      lens = np.array([len(i) for i in a]) # Only loop to get lengths
      C = lens.cumsum()
      N = lens.sum()
      
      # Create ID array such that the first element of each list is 0, 
      # the second element as 1 and so on. This is needed in such a format
      # for use with bincount later on.
      shifts_arr = np.ones(N,dtype=int)
      shifts_arr[C[:-1]] = -lens[:-1]+1
      id_arr = shifts_arr.cumsum()-1
      
      # Use bincount to get the summations and thus the 
      # averages across all lists based on their positions. 
      avg_out = np.bincount(id_arr,np.concatenate(a))/np.bincount(id_arr)
      

      -* 几乎是因为我们通过循环获取列表的长度,但其中涉及的计算量极少,因此一定不会对总运行时间产生太大影响。

      示例运行 -

      In [109]: a = [ [1, 2, 3],
           ...:       [2, 3, 4],
           ...:       [3, 4, 5, 6] ]
      
      In [110]: lens = np.array([len(i) for i in a])
           ...: C = lens.cumsum()
           ...: N = lens.sum()
           ...: 
           ...: shifts_arr = np.ones(N,dtype=int)
           ...: shifts_arr[C[:-1]] = -lens[:-1]+1
           ...: id_arr = shifts_arr.cumsum()-1
           ...: 
           ...: avg_out = np.bincount(id_arr,np.concatenate(a))/np.bincount(id_arr)
           ...: 
      
      In [111]: avg_out
      Out[111]: array([ 2.,  3.,  4.,  6.])
      

      【讨论】:

      • 我偷偷怀疑这是最快的:)
      • @PadraicCunningham 好吧,我希望如此! :D 仍然取决于 OP 的数据格式。
      【解决方案5】:

      您已经可以清理代码以计算最大长度:这一行就可以完成这项工作:

      len(max(a,key=len))
      

      结合其他答案,你会得到这样的结果:

      [np.mean([x[i] for x in a if len(x) > i]) for i in range(len(max(a,key=len)))]
      

      【讨论】:

        【解决方案6】:

        在您的测试阵列上:

        [np.mean([x[i] for x in a if len(x) > i]) for i in range(4)]
        

        返回

        [2.0, 3.0, 4.0, 6.0]
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-09-15
          • 1970-01-01
          • 2017-06-11
          • 2021-09-29
          • 2014-04-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多