Pythonic способ объединения данных без pandas / numpy

#python-3.x #data-science #binning

#python-3.x #наука о данных #объединение

Вопрос:

Я ищу способ объединить набор данных из нескольких сотен записей в 20 ячеек. Но без использования больших модулей, таких как pandas (вырезать) и numpy (оцифровывать). Может ли кто-нибудь придумать лучшее решение, чем 18 elifs?

Ответ №1:

Все, что вам нужно сделать, это выяснить, в какой ячейке находится каждый элемент. Это довольно тривиально, учитывая размер ячеек, если они однородны. Из вашего массива вы можете найти minval и maxval . Затем, binwidth = (maxval - minval) / nbins . Для элемента вашего массива elem с известным минимальным значением minval и шириной ячейки binwidth элемент попадет в номер ячейки int((elem - minval) / binwidth) . Это оставляет крайний случай , когда elem == maxval . В этом случае номер ячейки равен nbins ( nbins 1 th bin, потому что python основан на нуле), поэтому мы должны уменьшить номер ячейки только для этого одного случая.

Итак, мы можем написать функцию, которая делает это:

 import random

def splitIntoBins(arr, nbins, minval=None, maxval=None):
    minval = min(arr) if minval is None else minval # Select minval if specified, otherwise min of data
    maxval = max(arr) if maxval is None else maxval # Same for maxval
    
    binwidth = (maxval - minval) / nbins # Bin width
    allbins = [[] for _ in range(nbins)] # Pre-make a list-of-lists to hold values

    for elem in arr:
        binnum = int((elem - minval) // binwidth) # Find which bin this element belongs in
        binindex = min(nbins-1, binnum) # To handle the case of elem == maxval
        allbins[binindex].append(elem) # Add this element to the bin
    return allbins

# Make 1000 random numbers between 0 and 1
x = [random.random() for _ in range(1000)]

# split into 10 bins from 0 to 1, i.e. a bin every 0.1
b = splitIntoBins(x, 10, 0, 1)

# Get min, max, count for each bin
counts = [(min(v), max(v), len(v)) for v in b]
print(counts)
 

Это дает

 [(0.00017731201786974626, 0.09983758434153, 101),
 (0.10111204267013452, 0.19959594179848794, 97),
 (0.20089309189822557, 0.2990120768922335, 100),
 (0.3013915797055913, 0.39922131591077614, 90),
 (0.4009006835799309, 0.49969892298935836, 83),
 (0.501675740585966, 0.5999729295882031, 119),
 (0.6010149249108184, 0.7000366124696699, 120),
 (0.7008002068562794, 0.7970568220766774, 91),
 (0.8018697850229161, 0.8990963218226316, 99),
 (0.9000732426223624, 0.9967964437788829, 100)]
 

Что выглядит именно так, как мы и ожидаем.

Для неоднородных ячеек это уже не арифметический расчет. В этом случае элемент elem находится в ячейке, нижняя граница которой меньше elem , а верхняя — больше elem .

 def splitIntoBins2(arr, bins):
    binends = bins[1:]
    binstarts = bins[:-1]
    allbins = [[] for _ in binends] # Pre-make a list-of-lists to hold values

    for elem in arr:
        for i, (lower_bound, upper_bound) in enumerate(zip(binstarts, binends)):
            if upper_bound >= elem and lower_bound <= elem:
                allbins[i].append(elem) # Add this element to the bin
                break
    return allbins
 

Комментарии:

1. minval = minval or min(arr) является ошибкой , если minval есть 0 и min(arr) нет 0 . Всегда лучше (и даже немного быстрее) проверять minval is None . Вы могли бы использовать троичное выражение или просто оператор if-else.

2. Хороший улов @juanpa.arrivillaga