2015-02-23 18 views
14

Problema:Calcolare nuovo valore basato sul valore decrescente

Cosa ho piace fare è passo-passo ridurre un valore in una Series da una cifra base continuamente diminuendo.

non sono sicuro della terminologia per questo - pensavo che avrei potuto fare qualcosa con cumsum e diff ma penso che mi sto portando su un inseguimento oca selvatica lì ...

codice di partire :

import pandas as pd 

ALLOWANCE = 100 
values = pd.Series([85, 10, 25, 30]) 

uscita desiderata:

desired = pd.Series([0, 0, 20, 30]) 

Rationale:

partendo da una base di ALLOWANCE - ogni valore nella Series è ridotto della restante parte è l'assegno stesso, in modo verificano i seguenti passaggi:

  • Iniziare con 100 , siamo in grado di rimuovere completamente 85 così diventa 0, ora abbiamo lasciato come 15ALLOWANCE
  • Il valore successivo è 10 e abbiamo ancora 15 dispo ble, quindi questo diventa 0 di nuovo e abbiamo 5 a sinistra.
  • Il valore successivo è 25 - abbiamo lasciato solo 5, quindi questo diventa 20 e ora non abbiamo più alcuna autorizzazione.
  • Il valore successivo è 30 e, poiché non vi è alcuna tolleranza, il valore rimane come 30.
+0

Vorrei rinominare la variabile 'values' in' spese' e ​​la variabile 'desiderata' in' debiti', che in combinazione con 'allowance' consente al lettore di capire cosa stai cercando di realizzare senza nemmeno guardare il testo, imo. – mucaho

risposta

10

In seguito alla vostra idea iniziale di cumsum e diff, si potrebbe scrivere:

>>> (values.cumsum() - ALLOWANCE).clip_lower(0).diff().fillna(0) 
0  0 
1  0 
2 20 
3 30 
dtype: float64 

Questa è la somma cumulativa di values meno la franchigia.I valori negativi sono tagliati a zero (dato che non ci importa dei numeri fino a quando non abbiamo superato la nostra tolleranza). Da lì, puoi calcolare la differenza.

Tuttavia, se il primo valore può essere maggiore rispetto alla dose, la seguente variante a due righe è preferito:

s = (values.cumsum() - ALLOWANCE).clip_lower(0) 
desired = s.diff().fillna(s) 

Questo riempie il primo valore NaN con il "primo valore - assegno" valore. Quindi, nel caso in cui ALLOWANCE venga abbassato a 75, restituisce desired come Series([10, 10, 25, 30]).

+0

Questo non sembra gestire il primo elemento di' Series'> 'ALLOWANCE' :( –

+0

@JonClements devi solo aggiungere' .fillna (0) ' – EdChum

+0

@EdChum non puoi farlo - pensa io bisogno di usare simile alla risposta di Carsten, se il primo valore nella serie rimane '85', e l''INDENNITA' è 70, il risultato è' 0' - che non è corretto - dovrebbe essere '15' –

1

Dovrebbe funzionare con un while ciclo:

ii = 0 
while (ALLOWANCE > 0 and ii < len(values)): 
    if (ALLOWANCE > values[ii]): 
     ALLOWANCE -= values[ii] 
     values[ii] = 0 
    else: 
     values[ii] -= ALLOWANCE 
     ALLOWANCE = 0 
    ii += 1 
+2

Grazie. Mentre funzionerà, sto anche pianificando di fare altre operazioni in 'panda '- quindi, se possibile, sono davvero in cerca di una soluzione basata su' pandas'. –

5

Questo probabilmente non è così performante, ma al momento questo è un modo Pandas di fare questo usando rolling_apply:

In [53]: 

ALLOWANCE = 100 
def reduce(x): 
    global ALLOWANCE 
    # short circuit if we've already reached 0 
    if ALLOWANCE == 0: 
     return x 
    val = max(0, x - ALLOWANCE) 
    ALLOWANCE = max(0, ALLOWANCE - x) 
    return val 

pd.rolling_apply(values, window=1, func=reduce) 
Out[53]: 
0  0 
1  0 
2 20 
3 30 
dtype: float64 

Or più semplicemente:

In [58]: 

values.apply(reduce) 
Out[58]: 
0  0 
1  0 
2 20 
3 30 
dtype: int64 
+0

Probabilmente c'è un modo migliore per riscrivere la mia funzione, io non sono un esperto di python, stavo pensando che questo potesse essere riscritto usando un generatore ma per qualche motivo non funzionava del tutto. Idealmente farei un cortocircuito se la tolleranza è già 0 e restituisco il valore della riga passata – EdChum

+0

Mi ha certamente indicato in quella che sembra la giusta direzione e mi ha dato alcune idee ... grazie per molto - leggendo su 'rolling_apply 'now –

8

La tua idea con cumsum e diff funziona. Non sembra troppo complicato; non sono sicuro se c'è una soluzione ancora più breve. Per prima cosa calcoliamo la somma cumulativa, operiamo su quella, e quindi torniamo indietro (diff è in qualche modo la funzione inversa di cumsum).

import math 

c = values.cumsum() - ALLOWANCE 
# now we've got [-15, -5, 20, 50] 
c[c < 0] = 0 # negative values don't make sense here 

# (c - c.shift(1)) # <-- what I had first: diff by accident 

# it is important that we don't fill with 0, in case that the first 
# value is greater than ALLOWANCE 
c.diff().fillna(math.max(0, values[0] - ALLOWANCE)) 
Problemi correlati