Downsampling o número de entradas em uma lista (sem interpolação)
-
26-09-2019 - |
Pergunta
Eu tenho uma lista de Python com várias entradas, que eu preciso descer a amostra usando:
- Um número máximo de linhas. Por exemplo, limitando uma lista de 1234 entradas a 1000.
- Uma proporção das linhas originais. Por exemplo, tornando a lista 1/3 seu comprimento original.
(Eu preciso ser capaz de fazer os dois lados, mas apenas um é usado de cada vez).
Acredito que, para o número máximo de linhas, posso apenas calcular a proporção necessária e passar isso para o downsizer proporcional:
def downsample_to_max(self, rows, max_rows):
return downsample_to_proportion(rows, max_rows / float(len(rows)))
... Então, eu realmente só preciso de uma função de downsampling. Alguma dica, por favor?
EDITAR: A lista contém objetos, não valores numéricos, então não preciso interpolar. A queda de objetos está bem.
SOLUÇÃO:
def downsample_to_proportion(self, rows, proportion):
counter = 0.0
last_counter = None
results = []
for row in rows:
counter += proportion
if int(counter) != last_counter:
results.append(row)
last_counter = int(counter)
return results
Obrigado.
Solução
Mantenha um contador, que você incrementa pelo segundo valor. Piso de cada vez e produza o valor nesse índice.
Outras dicas
Você pode usar islice
a partir de itertools
:
from itertools import islice
def downsample_to_proportion(rows, proportion=1):
return list(islice(rows, 0, len(rows), int(1/proportion)))
Uso:
x = range(1,10)
print downsample_to_proportion(x, 0.3)
# [1, 4, 7]
Ao invés de islice()
+ list()
É mais eficiente usar a sintaxe da fatia diretamente se a entrada já for do tipo de sequência:
def downsample_to_proportion(rows, proportion):
return rows[::int(1 / proportion)]
Essa solução pode ser um pouco exagerada para o pôster original, mas pensei em compartilhar o código que tenho usado para resolver isso e problemas semelhantes.
É um pouco demorado (cerca de 90 linhas), mas se você costuma ter essa necessidade, deseja um Oneliner fácil de usar e precisa de um ambiente livre de dependência python puro, acho que pode ser útil.
Basicamente, a única coisa que você precisa fazer é passar sua lista para a função e dizer qual comprimento você deseja que sua nova lista seja, e a função também:
- redução do tamanho Sua lista soltando itens se o novo comprimento for menor, assim como as respostas anteriores já sugeridas.
- esticar/Upscale sua lista (o oposto de reduzir o tamanho) se o novo comprimento for maior, com a opção adicional que você pode decidir se deve:
- Interpolar linearmente BW Os valores conhecidos (escolhidos automaticamente se a lista contém ints ou flutuadores)
- duplique cada valor para que ocupem um tamanho proporcional da nova lista (escolhida automaticamente se a lista contiver não-numbers)
- separe os valores originais e deixe lacunas entre
Tudo é coletado dentro de uma função; portanto, se você precisar, basta copiar e colá -lo no seu script e você pode começar a usá -lo imediatamente.
Por exemplo, você pode dizer:
origlist = [0,None,None,30,None,50,60,70,None,None,100]
resizedlist = ResizeList(testlist, 21)
print(resizedlist)
e pegue
[0, 5.00000000001, 9.9999999999900009, 15.0, 20.000000000010001, 24.999999999989999, 30, 35.0, 40.0, 45.0, 50.0, 55.0, 60.0, 65.0, 70, 75.000000000010004, 79.999999999989996, 85.0, 90.000000000010004, 94.999999999989996, 100]
Observe que pequenas imprecisões ocorrerão devido a limitações de ponto flutuante. Além disso, escrevi isso para o Python 2.x, por assim dizer, para usá -lo no python 3.x, basta adicionar uma única linha que diz xrange = range
.
E aqui está um truque bacana para interpolar entre subitems posicionados em uma lista de listas. Por exemplo, você pode interpolar facilmente entre tuplas de cor RGB para criar um gradiente de cores de x nr de etapas. Assumindo uma lista de tuplas de cor RGB de 3 e uma variável de comprimento de gradiente desejada com que você faz isso:
crosssections = zip(*rgbtuples)
grad_crosssections = ( ResizeList(spectrum,GRADIENTLENGTH) for spectrum in crosssections )
rgb_gradient = [list(each) for each in zip(*grad_crosssections)]
Provavelmente poderia precisar de algumas otimizações, eu tive que fazer bastante experimentação. Se você sentir que pode melhorar, sinta -se à vontade para editar minha postagem. Aqui está o código:
def ResizeList(rows, newlength, stretchmethod="not specified", gapvalue=None):
"""
Resizes (up or down) and returns a new list of a given size, based on an input list.
- rows: the input list, which can contain any type of value or item (except if using the interpolate stretchmethod which requires floats or ints only)
- newlength: the new length of the output list (if this is the same as the input list then the original list will be returned immediately)
- stretchmethod: if the list is being stretched, this decides how to do it. Valid values are:
- 'interpolate'
- linearly interpolate between the known values (automatically chosen if list contains ints or floats)
- 'duplicate'
- duplicate each value so they occupy a proportional size of the new list (automatically chosen if the list contains non-numbers)
- 'spread'
- drags the original values apart and leaves gaps as defined by the gapvalue option
- gapvalue: a value that will be used as gaps to fill in between the original values when using the 'spread' stretchmethod
"""
#return input as is if no difference in length
if newlength == len(rows):
return rows
#set auto stretchmode
if stretchmethod == "not specified":
if isinstance(rows[0], (int,float)):
stretchmethod = "interpolate"
else:
stretchmethod = "duplicate"
#reduce newlength
newlength -= 1
#assign first value
outlist = [rows[0]]
writinggapsflag = False
if rows[1] == gapvalue:
writinggapsflag = True
relspreadindexgen = (index/float(len(rows)-1) for index in xrange(1,len(rows))) #warning a little hacky by skipping first index cus is assigned auto
relspreadindex = next(relspreadindexgen)
spreadflag = False
gapcount = 0
for outlistindex in xrange(1, newlength):
#relative positions
rel = outlistindex/float(newlength)
relindex = (len(rows)-1) * rel
basenr,decimals = str(relindex).split(".")
relbwindex = float("0."+decimals)
#determine equivalent value
if stretchmethod=="interpolate":
#test for gap
maybecurrelval = rows[int(relindex)]
maybenextrelval = rows[int(relindex)+1]
if maybecurrelval == gapvalue:
#found gapvalue, so skipping and waiting for valid value to interpolate and add to outlist
gapcount += 1
continue
#test whether to interpolate for previous gaps
if gapcount > 0:
#found a valid value after skipping gapvalues so this is where it interpolates all of them from last valid value to this one
startvalue = outlist[-1]
endindex = int(relindex)
endvalue = rows[endindex]
gapstointerpolate = gapcount
allinterpolatedgaps = Resize([startvalue,endvalue],gapstointerpolate+3)
outlist.extend(allinterpolatedgaps[1:-1])
gapcount = 0
writinggapsflag = False
#interpolate value
currelval = rows[int(relindex)]
lookahead = 1
nextrelval = rows[int(relindex)+lookahead]
if nextrelval == gapvalue:
if writinggapsflag:
continue
relbwval = currelval
writinggapsflag = True
else:
relbwval = currelval + (nextrelval - currelval) * relbwindex #basenr pluss interindex percent interpolation of diff to next item
elif stretchmethod=="duplicate":
relbwval = rows[int(round(relindex))] #no interpolation possible, so just copy each time
elif stretchmethod=="spread":
if rel >= relspreadindex:
spreadindex = int(len(rows)*relspreadindex)
relbwval = rows[spreadindex] #spread values further apart so as to leave gaps in between
relspreadindex = next(relspreadindexgen)
else:
relbwval = gapvalue
#assign each value
outlist.append(relbwval)
#assign last value
if gapcount > 0:
#this last value also has to interpolate for previous gaps
startvalue = outlist[-1]
endvalue = rows[-1]
gapstointerpolate = gapcount
allinterpolatedgaps = Resize([startvalue,endvalue],gapstointerpolate+3)
outlist.extend(allinterpolatedgaps[1:-1])
outlist.append(rows[-1])
gapcount = 0
writinggapsflag = False
else:
outlist.append(rows[-1])
return outlist
Não pode resolver o seu problema? Mais exemplos estão disponíveis aqui