Search code examples
pythonnamedtuple

list of namedtuples; how to calculate the sum of individual elements


Say I have namedtuple:

Trade = namedtuple('Trade', ['Ticker', 'Date', 'QTY', 'Sell', 'Buy', 'Profit'])

Is there any 'pythonic' way to do a one-liner sum of each 'summable' or 'selected' (QTY, Sell, Buy, Profit) elements in the list?

listofstuff = []
Trade1 = Trade(Ticker='foo', Date='{2020:12:24}', QTY=100, Sell=500.0, Buy=100.0, Profit=400.0)
Trade2 = Trade(Ticker='foo', Date='{2020:12:24}', QTY=50, Sell=50.0, Buy=500.0, Profit=-450.0)

listofstuff.append(Trade1)
listofstuff.append(Trade2)

Expected result:

Trade(Ticker='do not care', Date='do not care', QTY=150.0, Sell = 550.0, Buy = 600.0,0 Profit=-50.0)

I know I can do following which takes 4 lines of code:

tot_QTY = sum(i.QTY for i in listofstuff)
tot_Sell = sum(i.Sell for i in listofstuff)
tot_Buy = sum(i.Buy for i in listofstuff)
tot_Profit = sum(i.Profit for i in listofstuff)

x = Trade(Ticker=listofstuff[0].Ticker, Date=listofstuff[0].Date, QTY=tot_QTY,
          Sell=tot_Sell, Buy=tot_Buy, Profit=tot_Profit)

But would like to replace the sums with something more generic which takes only 1 line of code :)

total = sum(listofstuff) # most likely cannot calc sums of 'ticker' nor ' date' but I do not care since I can use original [0] items for those..

and then create 'x' like using the sum of the individual elements in the list

x = Trade(Ticker=listofstuff[0].Ticker, Date=listofstuff[0].Date, QTY=total.QTY,
          Sell=total.Sell, Buy=total.Buy, Profit=total.Profit)

Solution

  • If you don't care what value the non-numeric values will have, you could use zip to form a new tuple with the totals:

    R = Trade(*((max,sum)[isinstance(v[0],(int,float))](v) 
                for v in zip(*listofstuff)))
    
    print(R)
    Trade(Ticker='foo', Date='{2020:12:24}', QTY=150, Sell=550.0,
          Buy=600.0, Profit=-50.0)
    

    alternatively, you could place a None value in the non total fields:

    R = Trade(*( sum(v) if isinstance(v[0],(int,float)) else None 
                 for v in zip(*listofstuff)))
    
    print(R)
    Trade(Ticker=None, Date=None, QTY=150, Sell=550.0, Buy=600.0, Profit=-50.0)
    

    If the aggregation type varies with each field (e.g. min for some fields, sum for others, etc), you can prepare a dictionary of aggregation functions and use it in the comprehension:

    aggregate = {'QTY':sum, 'Sell':min, 'Buy':max, 'Profit':lambda v:sum(v)/len(v)}
    
    R = Trade(*(aggregate.get(f,lambda _:None)(v) 
                for f,v in zip(Trade._fields,zip(*listofstuff))))
    
    print(R)
    Trade(Ticker=None, Date=None, QTY=150, Sell=50.0, Buy=500.0,  Profit=-25.0)