Esta pregunta es muy similar a una que publiqué antes con solo un cambio. En lugar de hacer solo la diferencia absoluta para todas las columnas, también quiero encontrar la diferencia de magnitud para la columna 'Z', por lo que si la Z actual es 1.1x mayor que la anterior, manténgala.
(más contexto al problema)
Pandas que usan los valores de clasificación anteriores para filtrar la fila actual
df = pd.DataFrame({ 'rank': [1, 1, 2, 2, 3, 3], 'x': [0, 3, 0, 3, 4, 2], 'y': [0, 4, 0, 4, 5, 5], 'z': [1, 3, 1.2, 3.25, 3, 6], }) print(df) # rank xyz # 0 1 0 0 1.00 # 1 1 3 4 3.00 # 2 2 0 0 1.20 # 3 2 3 4 3.25 # 4 3 4 5 3.00 # 5 3 2 5 6.00Esto es lo que quiero que sea la salida
output = pd.DataFrame({ 'rank': [1, 1, 2, 3], 'x': [0, 3, 0, 2], 'y': [0, 4, 0, 5], 'z': [1, 3, 1.2, 6], }) print(output) # rank xyz # 0 1 0 0 1.0 # 1 1 3 4 3.0 # 2 2 0 0 1.2 # 5 3 2 5 6.00básicamente, lo que quiero que suceda es si el rango anterior tiene filas con x, y (+- 1 en ambos sentidos) Y z (<1.1z) para eliminarlo.
Entonces, para el rango de filas 1 CUALQUIER fila en el rango 2 que tenga cualquier combinación de x = (-1-1), y = (-1-1), z= (<1.1) O x = (2-5), y = (3-5), z= (<3.3) Quiero que se elimine
Solo toma un ajuste al término z de la ecuación lamda de la publicación vinculada:
return d.apply(lambda s: abs(d_prev-s)[['x', 'y', 'z']].le([1,1,.1*d_prev['z']]).all(1).any(), axis=1)Aquí está el código completo que funciona para mí:
df = pd.DataFrame({ 'rank': [1, 1, 2, 2, 2, 3, 3], 'x': [0, 3, 0, 3, 3, 4, 2], 'y': [0, 4, 0, 4, 4, 5, 5], 'z': [1, 3, 1.2, 3.3, 3.31, 3, 6], }) def check_previous_group(rank, d, groups): if not rank-1 in groups.groups: # check is a previous group exists, else flag all rows False (ie not to be dropped) return pd.Series(False, index=d.index) else: # get previous group (rank-1) d_prev = groups.get_group(rank-1) # get the absolute difference per row with the whole dataset # of the previous group: abs(d_prev-s) # if all differences are within 1/1/0.1*z for x/y/z # for at least one rows of the previous group # then flag the row to be dropped (True) return d.apply(lambda s: abs(d_prev-s)[['x', 'y', 'z']].le([1,1,.1*d_prev['z']]).all(1).any(), axis=1) groups = df.groupby('rank') mask = pd.concat([check_previous_group(rank, d, groups) for rank,d in groups]) df[~mask]Necesitas modificar ligeramente mi código anterior :
def check_previous_group(rank, d, groups): if not rank-1 in groups.groups: # check is a previous group exists, else flag all rows False (ie not to be dropped) return pd.Series(False, index=d.index) else: # get previous group (rank-1) d_prev = groups.get_group(rank-1) # get the absolute difference per row with the whole dataset # of the previous group: abs(d_prev-s) # if all differences are within 1/1/0.1*z for x/y/z # for at least one rows of the previous group # then flag the row to be dropped (True) return d.apply(lambda s: abs(d_prev-s)[['x', 'y', 'z']].le([1,1,.1*s['z']]).all(1).any(), axis=1) groups = df.groupby('rank') mask = pd.concat([check_previous_group(rank, d, groups) for rank,d in groups]) df[~mask]producción:
rank xyz 0 1 0 0 1.0 1 1 3 4 3.0 2 2 0 0 1.2 5 3 2 5 6.0Esto funciona para mí en Python 3.8.6
import pandas as pd dfg = df.groupby("rank") def filter_func(dfg): for g in dfg.groups.keys(): if g-1 in dfg.groups.keys(): yield ( pd.merge( dfg.get_group(g).assign(id = lambda df: df.index), dfg.get_group(g-1), how="cross", suffixes=("", "_prev") ).assign( cond = lambda df: ~( (df.x - df.x_prev).abs().le(1) & (df.y - df.y_prev).abs().le(1) & df.z.divide(df.z_prev).lt(1.1) ) ) ).groupby("id").agg( { **{"cond": "all"}, **{k: "first" for k in df.columns} }).loc[lambda df: df.cond].drop(columns = ["cond"]) else: yield dfg.get_group(g) pd.concat( filter_func(dfg), ignore_index=True )El resultado parece coincidir con lo que esperaba:
rank xyz 0 1 0 0 1.0 1 1 3 4 3.0 2 2 0 0 1.2 3 3 2 5 6.0Pequeña edición: en su pregunta parece que le importa el índice de fila. La solución que publiqué simplemente ignora esto, pero si desea conservarlo, simplemente guárdelo como una columna adicional en el marco de datos.
Aquí hay una solución usando numpy broadcasting :
# Initially, no row is dropped df['drop'] = False for r in range(df['rank'].min(), df['rank'].max()): # Find the x_min, x_max, y_min, y_max, z_max of the current rank cond = df['rank'] == r x, y, z = df.loc[cond, ['x','y','z']].to_numpy().T x_min, x_max = x + [[-1], [1]] # use numpy broadcasting to ±1 in one command y_min, y_max = y + [[-1], [1]] z_max = z * 1.1 # Find the x, y, z of the next rank. Raise them one dimension # so that we can make a comparison matrix again x_min, x_max, ... cond = df['rank'] == r + 1 if not cond.any(): continue x, y, z = df.loc[cond, ['x','y','z']].to_numpy().T[:, :, None] # Condition to drop a row drop = ( (x_min <= x) & (x <= x_max) & (y_min <= y) & (y <= y_max) & (z <= z_max) ).any(axis=1) df.loc[cond, 'drop'] = drop # Result df[~df['drop']]Una versión aún más condensada (y probablemente más rápida). Esta es una muy buena manera de desconcertar a tus futuros compañeros de equipo cuando lean el código:
r, x, y, z = df[['rank', 'x', 'y', 'z']].T.to_numpy() rr, xx, yy, zz = [col[:,None] for col in [r, x, y, z]] drop = ( (rr == r + 1) & (x-1 <= xx) & (xx <= x+1) & (y-1 <= yy) & (yy <= y+1) & (zz <= z*1.1) ).any(axis=1) # Result df[~drop] Lo que esto hace es comparar cada fila en df entre sí (incluido él mismo) y devolver True (es decir, descartar) si:
rank de la otra fila rank + 1 ; yx, y, z de la fila actual se encuentran dentro del rango especificado de los x, y, z de la otra filaHe modificado la función de mozway para que funcione de acuerdo a tus requerimientos.
# comparing 'equal' float values, may go wrong, that's why I am using this constant DELTA=0.1**12 def check_previous_group(rank, d, groups): if not rank-1 in groups.groups: # check if a previous group exists, else flag all rows False (ie not to be dropped) #return pd.Series(False, index=d.index) return pd.Series(False, index=d.index) else: # get previous group (rank-1) d_prev = groups.get_group(rank-1) # get the absolute difference per row with the whole dataset # of the previous group: abs(d_prev-s) # if differences in x and y are within 1 and z < 1.1*x # for at least one row of the previous group # then flag the row to be dropped (True) return d.apply(lambda s: (abs(d_prev-s)[['x', 'y']].le([1,1]).all(1)& (s['z']<1.1*d_prev['x']-DELTA)).any(), axis=1)pruebas,
>>> df = pd.DataFrame({ 'rank': [1, 1, 2, 2, 3, 3], 'x': [0, 3, 0, 3, 4, 2], 'y': [0, 4, 0, 4, 5, 5], 'z': [1, 3, 1.2, 3.25, 3, 6], }) >>> df rank xyz 0 1 0 0 1.00 1 1 3 4 3.00 2 2 0 0 1.20 3 2 3 4 3.25 4 3 4 5 3.00 5 3 2 5 6.00 >>> groups = df.groupby('rank') >>> mask = pd.concat([check_previous_group(rank, d, groups) for rank,d in groups]) >>> df[~mask] rank xyz 0 1 0 0 1.0 1 1 3 4 3.0 2 2 0 0 1.2 5 3 2 5 6.0 >>> df = pd.DataFrame({ 'rank': [1, 1, 2, 2, 3, 3], 'x': [0, 3, 0, 3, 4, 2], 'y': [0, 4, 0, 4, 5, 5], 'z': [1, 3, 1.2, 3.3, 3, 6], }) >>> df rank xyz 0 1 0 0 1.0 1 1 3 4 3.0 2 2 0 0 1.2 3 2 3 4 3.3 4 3 4 5 3.0 5 3 2 5 6.0 >>> groups = df.groupby('rank') >>> mask = pd.concat([check_previous_group(rank, d, groups) for rank,d in groups]) >>> df[~mask] rank xyz 0 1 0 0 1.0 1 1 3 4 3.0 2 2 0 0 1.2 3 2 3 4 3.3 5 3 2 5 6.0