Mi df se ve así:

import date time as dt

data = [{'expiry': dt.datetime(2020,6,26), 'strike': 137.5, 'diff': 0.797}, 
        {'expiry': dt.datetime(2020,6,26), 'strike': 138.0, 'diff': 0.305}, 
        {'expiry': dt.datetime(2020,6,26), 'strike': 138.5, 'diff': 0.188}, 
        {'expiry': dt.datetime(2020,6,26), 'strike': 139.0, 'diff': 0.688}, 
        {'expiry': dt.datetime(2020,7,24), 'strike': 137.5, 'diff': 0.805},
        {'expiry': dt.datetime(2020,7,24), 'strike': 138.0, 'diff': 0.305}, 
        {'expiry': dt.datetime(2020,7,24), 'strike': 138.5, 'diff': 0.203}, 
        {'expiry': dt.datetime(2020,7,24), 'strike': 139.0, 'diff': 0.703}]
df = pd.DataFrame(data).set_index('expiry')

Estoy buscando encontrar el mínimo por índice único (caducidad). Lo siguiente funciona pero es bastante lento. Buscando una forma más rápida de hacer esto, ya sea en Python puro, NumPy o pandas.

atm_df = pd.DataFrame()
for date in df.index.unique():
    _df = df.loc[date]
    atm_df = atm_df.append(_df.loc[(_df['diff'] == _df['diff'].min())])
atm_df

La salida deseada se ve así (pero no importa si es un df o un dict):

            strike  diff
expiry      
2020-06-26  138.5   0.188
2020-07-24  138.5   0.203
0
steff 4 jun. 2020 a las 06:13

3 respuestas

La mejor respuesta

Puede utilizar Pandas groupby en el índice y el agregado con min para obtener el mínimo para la columna diff. compare el resultado de la agrupación con los valores en diff, luego indexe el marco de datos con el booleano resultante.

df.loc[df['diff'].eq(df.groupby(level=0)['diff'].min())]

           strike   diff
expiry      
2020-06-26  138.5   0.188
2020-07-24  138.5   0.203

Solo una experiencia de aprendizaje para mí: lo probé en Python puro:

from itertools import groupby
from operator import itemgetter

#convert to dict: 
m = df.reset_index().to_numpy()

#we'll use itertools groupby
#data is already sorted so I wont bother with that
#groupby requires data to be sorted

#the first item in the sublist, expiry
#will be our grouping key
#this is our expiry value

grp_key = itemgetter(0)

#we need the rows with the minimum for diff
diff_min = itemgetter(-1)

columns = df.reset_index().columns

outcome = [dict(zip(columns, min(value,key=diff_min)))
           for key,value 
           in groupby(m, grp_key)
           ]

outcome

    [{'expiry': Timestamp('2020-06-26 00:00:00'), 'strike': 138.5, 'diff': 0.188},
 {'expiry': Timestamp('2020-07-24 00:00:00'), 'strike': 138.5, 'diff': 0.203}]

ACTUALIZACIÓN: Gracias @steff por señalarme hacia los diccionarios. El cálculo se puede resolver allí antes de leer en Pandas, si es necesario. Usaremos los mismos pasos que involucran itemgetter y groupby de itertools

#sort data
data = sorted(data, key = itemgetter('expiry'))

outcome = [min(value, key = itemgetter("diff"))
           for _,value 
           in groupby(data,key=itemgetter("expiry"))]

outcome

[{'expiry': datetime.datetime(2020, 6, 26, 0, 0),
  'strike': 138.5,
  'diff': 0.188},
 {'expiry': datetime.datetime(2020, 7, 24, 0, 0),
  'strike': 138.5,
  'diff': 0.203}]
1
sammywemmy 8 jun. 2020 a las 12:02

Uno basado en np.minimum.reduceat -

sidx = df.index.argsort()
df_s = df.iloc[sidx]
I = df_s.index.values

cutidx = np.flatnonzero(np.r_[True,I[:-1]!=I[1:]])
out = np.minimum.reduceat(df_s.values, cutidx, axis=0)
df_out = pd.DataFrame(out, index=I[cutidx], columns=df_s.columns)

Si el marco de datos de entrada ya está ordenado por index, use df como df_s directamente.

2
Divakar 4 jun. 2020 a las 05:20

min funciona con level, y luego puede usar eq para comparar la serie con el min extraído:

df[df['diff'].eq(df['diff'].min(level=0))]

Salida:

            strike   diff
expiry                   
2020-06-26   138.5  0.188
2020-07-24   138.5  0.203
2
Quang Hoang 4 jun. 2020 a las 03:37