A partir de este DataFrame con nombre df
SampleTargetCtCt MeanC41B2M20.64239920.680149C41B2M20.71790120.680149C42ULK129.09788329.110802C42ULK129.12372229.110802C43TBP22.12641221,4221565C43TBP20.71790121,4221565
La columna Ct Mean es la media de los dos valores de la columna Ct de la misma Sample y Target (esto te lo da por defecto el .xlsx que importo al pandas).
Mi intención es comprobar si la diferencia entre los dos valores de la columna Ct para la misma muestra y target no es mayor a +-1 dos a dos. Por ejemplo, para C41 (B2M) la diferencia entre 20.642399 y 20.717901 es menor a 1 con lo cual debería devolver el valor de Ct tal cual. En cambio, para C43 (TBP) la diferencia entre 22.126412 y 20.717901 es mayor 1 y sustituir los dos valores de Ct para C43 por "Undetermined". El resultado que debería darme sería este:
SampleTargetCtCt MeanC41B2M20.64239920.680149C41B2M20.71790120.680149C42ULK129.09788329.110802C42ULK129.12372229.110802C43TBPUndeterminedUndeterminedC43TBPUndeterminedUndetermined
He intentado de varias formas realizar una resta entre dos elementos de una misma columna de un dataframe pero no he sido capaz. La primera era aplicar un bucle para esa columna que hiciese la diferencia entre los dos valores haciendo un salto para que luego haga los dos siguientes:
def loop(i):
for i in range(0,96,2):
if i-(i+1)>1 or i-(i+1)<(-1):
i=="Undetermined"
else:
return i
prueba = df["Ct"].apply(loop)
prueba
Imprime:
0 0
1 0
2 0
3 0
4 0
..
91 0
92 0
93 0
94 0
95 0
Name: Ct, Length: 96, dtype: int64
NOTA* Mi dataframe tiene 96 filas. Solo he puesto un head con las 6 primeras para el ejemplo. Al imprimir me da todo 0. He estado buscando y vi que existe el método .diff que permite restar el valor de un elemento menos el valor del elemento anterior, pero no sé como aplicarlo. Otra manera que pensé es utilizar:
df["Ct"].sub(df[0,len(df),2], axis=0)
Obviamente da error y tampoco está bien la sintaxis.
def myfunc(g):
if any(g.Ct.diff().abs()>1):
g["Ct Mean"] = "Undetermined"
return g
df = df.groupby(["Sample", "Target"]).apply(myfunc)
Si df inicialmente contiene:
Sample Target Ct Ct Mean
0 C41 B2M 20.642399 20.680149
1 C41 B2M 20.717901 20.680149
2 C42 ULK1 29.097883 29.110802
3 C42 ULK1 29.123722 29.110802
4 C43 TBP 22.126412 21,4221565
5 C43 TBP 20.717901 21,4221565
el resultado del código anterior produce:
Sample Target Ct Ct Mean
0 C41 B2M 20.642399 20.680149
1 C41 B2M 20.717901 20.680149
2 C42 ULK1 29.097883 29.110802
3 C42 ULK1 29.123722 29.110802
4 C43 TBP 22.126412 Undetermined
5 C43 TBP 20.717901 Undetermined
Como ves todo se resuelve en una línea:
df.groupby(["Sample", "Target"]).apply(myfunc)
Lo que hace esto es agrupar el dataframe por "Sample" y "Target" de modo que reúne en varios "sub-dataframes" las filas que tengan el mismo valor en "Sample" y "Target". A cada uno de los "sub-dataframes" resultantes (que llamaremos en adelante "grupos") se les aplica la función myfunc.
Esta función recibe por tanto en su parámetro g un grupo, que es en realidad un dataframe pero "filtrado" de modo que tiene solo un par de elementos con el mismo Sample y Target, al menos en este caso son solo un par de elementos. De forma más general reciben un dataframe con un número arbitrario de filas, con las mismas columnas que el df original, y con el mismo valor de "Sample" y "Target" en todas las filas.
Lo que hace la función es determinar si en ese grupo hay que cambiar el valor de la columna "Ct Mean" para ponerle "Undetermined" o hay que dejarlo como estaba. Después retorna el grupo en cuestión, para que .apply() lo vaya concatenando de nuevo con los restantes grupos para crear el dataframe con el resultado.
La clave para determinar si hay que poner o no "Undetermined" es la línea siguiente:
if any(g.Ct.diff().abs()>1):
g.Ct es la columna Ct del grupo recibido. Al aplicarle .diff() se resta en esa columna a cada elemento el anterior. El primero no tiene anterior por lo que el resultado sale NaN, pero en los siguientes el resultado será la diferencia. Se tiene así una columna de diferencias. A esa columna se le aplica .abs() para quedarse con el valor absoluto de modo que el signo no influya. Se tiene por tanto una columna de números (el primero de ellos NaN).
Se compara la columna con >1 lo que nos da una nueva columna pero esta vez de booleanos. Para cada elemento de la diferencia que sea mayor de 1, habrá un True (y el resto serán False). El primero que es NaN dará False siempre. En tu caso sólo habrá un elemento más (porque sólo hay dos filas en cada grupo), pero de forma general podríamos tener una columna con muchos booleanos.
Esa columna se le pasa a any() que devuelve True si hay al menos un True entre los elementos. Sólo si todos son False devolverá False.
El resultado es que si entre los valores retornados por .diff() hay alguno mayor de 1, el if se ejecutará y entonces hará:
g["Ct Mean"] = "Undetermined"
que asigna ese valor a la columna completa, es decir, a todas las filas de ese grupo. Si el if no se cumple, g no se toca.
Finalmente la función retorna g (haya sido modificado o no).