¿Qué reglas utiliza Pandas para generar una vista frente a una copia?
Estoy confundido acerca de las reglas que usa Pandas al decidir que una selección de un marco de datos es una copia del marco de datos original o una vista del original.
Si tengo, por ejemplo,
df = pd.DataFrame(np.random.randn(8,8), columns=list('ABCDEFGH'), index=range(1,9))
Entiendo que a query
devuelve una copia para que algo como
foo = df.query('2 < index <= 5')
foo.loc[:,'E'] = 40
no tendrá ningún efecto en el marco de datos original df
. También entiendo que los sectores escalares o con nombre devuelven una vista, por lo que las asignaciones a estos, como
df.iloc[3] = 70
o
df.ix[1,'B':'E'] = 222
cambiará df
. Pero estoy perdido cuando se trata de casos más complicados. Por ejemplo,
df[df.C <= df.B] = 7654321
cambios df
, pero
df[df.C <= df.B].ix[:,'B':'E']
no es.
¿Hay alguna regla simple que esté usando Pandas y que simplemente me falta? ¿Qué está pasando en estos casos específicos? y, en particular, ¿cómo cambio todos los valores (o un subconjunto de valores) en un marco de datos que satisface una consulta particular (como intento hacer en el último ejemplo anterior)?
Nota: Esto no es lo mismo que esta pregunta ; y he leído la documentación , pero no me aclara nada. También leí las preguntas "relacionadas" sobre este tema, pero todavía me falta la regla simple que usa Pandas y cómo la aplicaría para, por ejemplo, modificar los valores (o un subconjunto de valores). en un marco de datos que satisface una consulta particular.
Aquí están las reglas, anulación posterior:
Todas las operaciones generan una copia.
Si
inplace=True
se proporciona, se modificará in situ; Sólo algunas operaciones soportan esto.Un indexador que establece, por ejemplo,
.loc/.iloc/.iat/.at
se establecerá en su lugar.Un indexador que llega a un objeto de un solo tipo es casi siempre una vista (dependiendo del diseño de la memoria, puede que no lo sea, por eso no es confiable). Esto es principalmente por eficiencia. (el ejemplo anterior es para
.query
; esto siempre devolverá una copia tal como la evalúanumexpr
)Un indexador que llega a un objeto con varios tipos es siempre una copia.
Tu ejemplo dechained indexing
df[df.C <= df.B].loc[:,'B':'E']
No se garantiza que funcione (y por lo tanto nunca debes hacer esto).
En su lugar haz:
df.loc[df.C <= df.B, 'B':'E']
ya que esto es más rápido y siempre funcionará
La indexación encadenada consta de 2 operaciones de Python separadas y, por lo tanto, los pandas no pueden interceptarla de manera confiable (a menudo obtendrá un SettingWithCopyWarning
, pero tampoco es 100% detectable). Los documentos de desarrollo que usted señaló ofrecen una explicación mucho más completa.
Desde pandas 1.5.0, pandas tiene el modo Copia en escritura (CoW) que hace que cualquier marco de datos/serie derivado de otro se comporte como una copia en las vistas. Cuando está habilitado, se crea una copia solo si los datos se comparten con otro marco de datos/serie. Con CoW deshabilitado, operaciones como la división crean una vista (y cambian inesperadamente el original si se cambia el nuevo marco de datos), pero con CoW, esto crea una copia.
pd.options.mode.copy_on_write = False # disable CoW (this is the default as of pandas 2.0)
df = pd.DataFrame({'A': range(4), 'B': list('abcd')})
df1 = df.iloc[:4] # view
df1.iloc[0] = 100
df.equals(df1) # True <--- df changes together with df1
pd.options.mode.copy_on_write = True # enable CoW (this is planned to be the default by pandas 3.0)
df = pd.DataFrame({'A': range(4), 'B': list('abcd')})
df1 = df.iloc[:4] # copy because data is shared
df1.iloc[0] = 100
df.equals(df1) # False <--- df doesn't change when df1 changes
Una consecuencia es que las operaciones de los pandas son más rápidas con CoW. En el siguiente ejemplo, en el primer caso (cuando CoW está deshabilitado), todos los pasos intermedios crean copias, mientras que en el último caso (cuando CoW está habilitado), se crea una copia solo en la asignación (todos los pasos intermedios están en vistas). Puede ver que hay una diferencia en el tiempo de ejecución debido a eso (en el último caso, los datos no se copiaron innecesariamente).
df = pd.DataFrame({'A': range(1_000_000), 'B': range(1_000_000)})
%%timeit
with pd.option_context('mode.copy_on_write', False): # disable CoW in a context manager
df1 = df.add_prefix('col ').set_index('col A').rename_axis('index col').reset_index()
# 30.5 ms ± 561 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%%timeit
with pd.option_context('mode.copy_on_write', True): # enable CoW in a context manager
df2 = df.add_prefix('col ').set_index('col A').rename_axis('index col').reset_index()
# 18 ms ± 513 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)