Estoy tratando de encontrar una forma de poner un nan en columnas que no existen entre 2 valores de inicio/final en otra columna para cada fila. Digamos que tengo el siguiente marco de datos:
df = pd.DataFrame({'39' : [1, np.nan, 3], '40' : [2, 4, 5], '41' : [3, 1, 4], '42' : [2, 5, 2], '43' : [1, 1, np.nan], 'start' : [39, 40, 41], 'end' : [41, 41, 43]}) 39 40 41 42 43 start end 0 1.0 2 3 2 1 39 41 1 NaN 4 1 5 1 40 41 2 3.0 5 4 2 3 41 43Quiero poner un nan en las columnas numeradas que no están entre los números de columna de inicio/fin (inclusive), para obtener lo siguiente:
39 40 41 42 43 start end 0 1.0 2.0 3 NaN NaN 39 41 1 NaN 4.0 1 NaN NaN 40 41 2 NaN NaN 4 2.0 3.0 41 43Actualmente, la única forma en que puedo pensar en hacer esto sería iterar a través de las filas o columnas para verificar si entre el inicio y el final o no, pero sé que iterar a través de marcos de datos es una mala práctica. Podría convertir las columnas en listas e iterarlas y reasignarlas, pero me pregunto si hay una manera más eficiente de lograr esto.
Editar: debo tener en cuenta que las columnas numéricas son números de semana, por lo que es posible que superen un año (por ejemplo, 51, 52, 1, 2, 3, luego el inicio podría ser 51 y el final podría ser 1). Me pregunto si necesito hacer una lista de los números de columna para mantener antes de hacer esto, ya que usar < o > no funcionará en este caso.
Un ejemplo de esto:
df2 = pd.DataFrame({'51' : [1, np.nan, 3], '52' : [2, 4, 5], '1' : [3, 1, 4], '2' : [2, 5, 2], '3' : [1, 1, 3], 'start' : [51, 52, 52], 'end' : [1, 2, 1]}) 51 52 1 2 3 start end 0 1.0 2 3 2 1 51 1 1 NaN 4 1 5 1 52 2 2 3.0 5 4 2 3 52 1Producción:
51 52 1 2 3 start end 0 1.0 2 3 NaN NaN 51 1 1 NaN 4 1 5.0 NaN 52 2 2 NaN 5 4 NaN NaN 52 1Podemos hacer uso de stack y unstack aquí:
df = df.set_index(["start", "end"]).stack() idx = df.index values = idx.get_level_values(2).astype(int) start = idx.get_level_values(0) end = idx.get_level_values(1) df.where((values >= start) & (values <= end)).unstack().reset_index() start end 39 40 41 42 43 0 39 41 1.0 2.0 3.0 NaN NaN 1 40 41 NaN 4.0 1.0 NaN NaN 2 41 43 NaN NaN 4.0 2.0 NaNfor column in ['39', '40', '41', '42', '43']: df[column].loc[(float(column) < df['start']) | (float(column) > df['end'])] = np.NaNimprimirá:
39 40 41 42 43 start end 0 1.0 2.0 3.0 NaN NaN 39 41 1 NaN 4.0 1.0 NaN NaN 40 41 2 NaN NaN 4.0 2.0 NaN 41 43Solución numpy con comparación entre inicio y final:
df.columns = df.columns[:-2].astype(int).tolist() + df.columns[-2:].tolist() s = df['start'].to_numpy() e = df['end'].to_numpy() cols = df.columns[:-2].to_numpy() m = (s[:, None] <= cols) & (e[:, None] >= cols) df.iloc[:, :-2] = df.iloc[:, :-2].where(m) print (df) 39 40 41 42 43 start end 0 1.0 2.0 3 NaN NaN 39 41 1 NaN 4.0 1 NaN NaN 40 41 2 NaN NaN 4 2.0 NaN 41 43 EDITAR: si no es posible, compare por valor con la suma acumulada, para e desde atrás y pruebe si 1 en ambas máscaras:
s = df['start'].astype(str).to_numpy() e = df['end'].astype(str).to_numpy() cols = df.columns[:-2].to_numpy() m1 = np.cumsum((s[:, None] == cols), axis=1) == 1 m2 = np.cumsum((e[:, None] == cols[::-1]), axis=1)[:, ::-1] == 1 m = m1 & m2 df.iloc[:, :-2] = df.iloc[:, :-2].where(m) print (df) 51 52 1 2 3 start end 0 1.0 2 3 NaN NaN 51 1 1 NaN 4 1 5.0 NaN 52 2 2 NaN 5 4 NaN NaN 52 1