Python: 计算列表中不同长度的子列表的第n个元素的平均值

4
假设我有以下列表的列表:
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个“列”,因此效率非常重要:-)。
6个回答

4

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(类似False的值),在这种情况下您需要使用列表推导式进行过滤,但速度仍然更快:
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))

正是我所需要的!非常感谢 :) - Laurens Jansma
仅供参考,izip_longest 在 Python 3 中更名为 zip_longest - ihavenoidea

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.])

我有一种隐秘的感觉,这是最快的 :) - Padraic Cunningham
@PadraicCunningham 嗯,我希望如此!:D 但仍取决于OP所拥有的数据格式。 - Divakar

3

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

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)))]

2

您也可以避免使用掩码数组,而是使用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)]

1

在你的测试数组上:

[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]

1
如果您使用的是Python版本大于等于3.4,则导入statistics模块。
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]]

遍历 myList
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")

请注意索引从零开始,因此例如如果您要查找第五个列表的平均值,它将位于索引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)显示第5个列表的平均值,作为mean_nth(myList, 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个元素表示索引中的第4个元素(0, 1, 2, 3, 4),或者第n个元素表示(n-1)th元素,让我们创建一个函数。
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

我不明白为什么有人会给它点踩,为什么不留下任何评论和理由! - harshvardhan
不是我的代码,但如果你尝试运行它,也许就会明白为什么了。 - Padraic Cunningham

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