Pandas: .loc [...] = valor devuelve SettingWithCopyWarning

Creado en 8 sept. 2017  ·  8Comentarios  ·  Fuente: pandas-dev/pandas

Muestra de código

# My code
df.loc[0, 'column_name'] = 'foo bar'

Descripción del problema

Este código en Pandas 20.3 lanza SettingWithCopyWarning y sugiere

"Intente usar .loc[row_indexer,col_indexer] = value lugar".

Ya lo estoy haciendo, parece que hay un pequeño error. Yo uso Jupyter.
¡Gracias! :)

Salida de pd.show_versions()


cometer: Ninguno
python: 3.6.1.final.0
bits de pitón: 64
SO: Windows
Versión del SO: 8.1
máquina: AMD64
Procesador: Intel64 Family 6 Model 61 Stepping 4, GenuineIntel
byteorder: pequeño
LC_ALL: Ninguno
LANG: Ninguno
LOCAL: Ninguno Ninguno

pandas: 0.20.1
pytest: 3.0.7
pip: 9.0.1
herramientas de configuración: 35.0.2
Cython: 0.25.2
numpy: 1.12.1
scipy: 0.19.0
xarray: Ninguno
IPython: 5.3.0
esfinge: 1.5.6
chivo expiatorio: 0.4.1
dateutil: 2.6.0
pytz: 2017.2
blosc: Ninguno
cuello de botella: 1.2.1
tablas: 3.2.2
numexpr: 2.6.2
pluma: ninguna
matplotlib: 2.0.2
openpyxl: Ninguno
xlrd: 1.0.0
xlwt: 1.2.0
xlsxwriter: 0.9.6
lxml: 3.7.3
bs4: 4.6.0
html5lib: 0.999
sqlalchemy: 1.1.9
pymysql: Ninguno
psycopg2: Ninguno
jinja2: 2.9.6
s3fs: Ninguno
pandas_gbq: Ninguno
pandas_datareader: Ninguno

Indexing Usage Question

Comentario más útil

El problema aquí es que está cortando su marco de datos primero con .loc en la línea 4. El intento de asignar valores a ese sector.

df_c = df.loc[df.encountry == country, :]

Pandas no está 100% seguro de si desea asignar valores solo a su segmento df_c , o hacer que se propague hasta el df . Para evitar esto, cuando asigne df_c asegúrese de decirle a los pandas que es su propio marco de datos (y no un segmento) usando

df_c = df.loc[df.encountry == country, :].copy()

Hacer esto solucionará su error. Agregaré un breve ejemplo para ayudar a explicar lo anterior, ya que he notado que muchos usuarios se confunden con los pandas en este aspecto.

Ejemplo con datos inventados

>>> import pandas as pd
>>> df = pd.DataFrame({'A':[1,2,3,4,5], 'B':list('QQQCC')})
>>> df
   A  B
0  1  Q
1  2  Q
2  3  Q
3  4  C
4  5  C
>>> df.loc[df['B'] == 'Q', 'new_col'] = 'hello'
>>> df
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q   hello
3  4  C     NaN
4  5  C     NaN

¡Entonces lo anterior funciona como esperamos! Ahora intentemos un ejemplo que refleje lo que intentó hacer con sus datos.

>>> df = pd.DataFrame({'A':[1,2,3,4,5], 'B':list('QQQCC')})
>>> df_q = df.loc[df['B'] == 'Q']
>>> df_q
   A  B
0  1  Q
1  2  Q
2  3  Q
>>> df_q.loc[df['A'] < 3, 'new_col'] = 'hello'
/Users/riddellcd/anaconda/lib/python3.6/site-packages/pandas/core/indexing.py:337: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  self.obj[key] = _infer_fill_value(value)

>>> df_q
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN

¡Parece que tenemos el mismo error! ¡Pero cambió df_q como esperábamos! Esto se debe a que df_q es una porción de df entonces, aunque estamos usando .loc [] df_q pandas nos advierte que no propagará los cambios hacia arriba. hasta df . Para evitar esto, debemos ser más explícitos y decir que df_q es su propio marco de datos, separado de df al declararlo explícitamente.

Comencemos desde df_q pero usemos .copy() esta vez.

>>> df_q = df.loc[df['B'] == 'Q'].copy()
>>> df_q
   A  B
0  1  Q
1  2  Q
2  3  Q

Lets try to reassign our value now!
>>> df_q.loc[df['A'] < 3, 'new_col'] = 'hello'
>>> df_q
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN

Esto funciona sin errores porque les hemos dicho a los pandas que df_q es independiente de df

Si de hecho desea que estos cambios en df_c propaguen hasta df es otro punto por completo y responderá si lo desea.

Todos 8 comentarios

@NadiaRom ¿Puede dar un ejemplo completo? Es difícil decirlo con certeza, pero sospecho que df provino de una operación que puede ser una vista o una copia. Por ejemplo:

In [8]: df = pd.DataFrame({"A": [1, 2], "B": [3, 4], "C": [4, 5]})

In [9]: df1 = df[['A', 'B']]

In [10]: df1.loc[0, 'A'] = 5
/Users/taugspurger/Envs/pandas-dev/lib/python3.6/site-packages/pandas/pandas/core/indexing.py:180: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  self._setitem_with_indexer(indexer, value)
/Users/taugspurger/Envs/pandas-dev/bin/ipython:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  #!/Users/taugspurger/Envs/pandas-dev/bin/python3.6

Así que estamos actualizando df1 correctamente. La ambigüedad es si df también se actualizará o no. Creo que te está pasando algo similar, pero sin un ejemplo reproducible es difícil decirlo con certeza.

@TomAugspurger Aquí está el código, en general, nunca asigno valores a pandas sin .loc

df = pd.read_csv('df_unicities.tsv', sep='\t')
df.replace({'|': '--'}, inplace=True)

df_c = df.loc[df.encountry == country, : ]

df_c['sort'] = (df_c.encities_ua == 'all').astype(int) # new column
df_c['sort'] += (df_c.encities_foreign == 'all').astype(int)
df_c.sort_values(by='sort', inplace=True)

# ---end of chunk, everything is fine ---

if df_c.encities_foreign.str.contains('all').sum() < len(df_c):
    df_c.loc[df_c.encities_foreign.str.contains('all'), 'encities_foreign'] = 'other'
    df_c.loc[df_c.cities_foreign.str.contains('всі'), 'cities_foreign'] = 'інші'
else:
    df_c.loc[df_c.encities_foreign.str.contains('all'), 'encities_foreign'] = country
    df_c.loc[df_c.cities_foreign.str.contains('всі'), 'cities_foreign'] = df_c.country.iloc[0]

if df_c.encities_ua.str.contains('all').sum() < len(df_c):
    df_c.loc[df_c.encities_ua.str.contains('all'), 'encities_ua'] = 'other'
    df_c.loc[df_c.cities_ua.str.contains('всі'), 'cities_ua'] = 'інші'
else:
    df_c.loc[df_c.encities_ua.str.contains('all'), 'encities_ua'] = 'Ukraine'
    df_c.loc[df_c.cities_ua.str.contains('всі'), 'cities_ua'] = 'Україна'

# Warning after it

¡Gracias por la rápida respuesta!

El problema aquí es que está cortando su marco de datos primero con .loc en la línea 4. El intento de asignar valores a ese sector.

df_c = df.loc[df.encountry == country, :]

Pandas no está 100% seguro de si desea asignar valores solo a su segmento df_c , o hacer que se propague hasta el df . Para evitar esto, cuando asigne df_c asegúrese de decirle a los pandas que es su propio marco de datos (y no un segmento) usando

df_c = df.loc[df.encountry == country, :].copy()

Hacer esto solucionará su error. Agregaré un breve ejemplo para ayudar a explicar lo anterior, ya que he notado que muchos usuarios se confunden con los pandas en este aspecto.

Ejemplo con datos inventados

>>> import pandas as pd
>>> df = pd.DataFrame({'A':[1,2,3,4,5], 'B':list('QQQCC')})
>>> df
   A  B
0  1  Q
1  2  Q
2  3  Q
3  4  C
4  5  C
>>> df.loc[df['B'] == 'Q', 'new_col'] = 'hello'
>>> df
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q   hello
3  4  C     NaN
4  5  C     NaN

¡Entonces lo anterior funciona como esperamos! Ahora intentemos un ejemplo que refleje lo que intentó hacer con sus datos.

>>> df = pd.DataFrame({'A':[1,2,3,4,5], 'B':list('QQQCC')})
>>> df_q = df.loc[df['B'] == 'Q']
>>> df_q
   A  B
0  1  Q
1  2  Q
2  3  Q
>>> df_q.loc[df['A'] < 3, 'new_col'] = 'hello'
/Users/riddellcd/anaconda/lib/python3.6/site-packages/pandas/core/indexing.py:337: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  self.obj[key] = _infer_fill_value(value)

>>> df_q
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN

¡Parece que tenemos el mismo error! ¡Pero cambió df_q como esperábamos! Esto se debe a que df_q es una porción de df entonces, aunque estamos usando .loc [] df_q pandas nos advierte que no propagará los cambios hacia arriba. hasta df . Para evitar esto, debemos ser más explícitos y decir que df_q es su propio marco de datos, separado de df al declararlo explícitamente.

Comencemos desde df_q pero usemos .copy() esta vez.

>>> df_q = df.loc[df['B'] == 'Q'].copy()
>>> df_q
   A  B
0  1  Q
1  2  Q
2  3  Q

Lets try to reassign our value now!
>>> df_q.loc[df['A'] < 3, 'new_col'] = 'hello'
>>> df_q
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN

Esto funciona sin errores porque les hemos dicho a los pandas que df_q es independiente de df

Si de hecho desea que estos cambios en df_c propaguen hasta df es otro punto por completo y responderá si lo desea.

@CRiddler ¡ Genial, gracias !
Como mencionaste, encadenado .loc nunca ha arrojado resultados inesperados. Según tengo entendido, .copy() garantiza a Pandas que tratamos el df_sliced_once como un objeto separado y no tenemos la intención de cambiar el df completo inicial. Por favor corrija si confundí algo.

la documentación está aquí http://pandas.pydata.org/pandas-docs/stable/indexing.html#returning -a-view-versus-a-copy y @CRiddler tiene una buena explicación. En general, NO debe usar inplace en absoluto.

Si de hecho desea que estos cambios en df_c propaguen hasta df es otro punto por completo y responderá si lo desea.

@CRiddler Gracias, su respuesta es mejor que las de Stack Overflow. ¿Podría agregar cuando desee propagar al marco de datos inicial o dar una indicación de cómo se hace?

@persep En general, no me gusta convertir problemas en subprocesos de stackoverflow para obtener ayuda, pero parece que este problema ha recibido bastante atención desde la última publicación, así que seguiré adelante y publicaré mi método para abordar este tipo de problema en pandas. Por lo general, hago esto al no subdividir el marco de datos en variables separadas, pero en cambio convierto las máscaras en variables, luego combino las máscaras según sea necesario y establezco valores basados ​​en esas máscaras para asegurar que los cambios ocurran en el marco de datos original, y no en alguna copia flotando alrededor .

Datos originales:

>>>import pandas as pd
>>> df = pd.DataFrame({'A':[1,2,3,4,5], 'B':list('QQQCC')})
>>> df
   A  B
0  1  Q
1  2  Q
2  3  Q
3  4  C
4  5  C

Recuerde que la creación de un marco de datos temporal NO propagará los cambios
Como se muestra en el ejemplo anterior, esto hace cambios solo a df_q y genera una advertencia de pandas (no se copia / pega aquí). Y NO propaga ningún cambio a df

>>> df_q = df.loc[df["B"] == "Q"]
>>> df_q.loc[df["A"] < 3, "new_column"] = "hello"

# df remains unchanged because we only made changes to `df_q`
>>> df
   A  B
0  1  Q
1  2  Q
2  3  Q
3  4  C
4  5  C

Que yo sepa, no hay forma de usar el mismo código que el anterior y forzar que los cambios se propaguen nuevamente al marco de datos original.

Sin embargo, si cambiamos un poco nuestro pensamiento y trabajamos con máscaras en lugar de subconjuntos completos, podemos lograr el resultado deseado. Si bien esto no necesariamente "propaga" los cambios al marco de datos original desde un subconjunto, nos aseguramos de que cualquier cambio que hagamos suceda en el marco de datos original df . Para hacer esto, primero creamos máscaras, luego las aplicamos cuando queremos hacer un cambio en ese subconjunto de df

>>> q_mask = df["B"] == "Q"
>>> a_mask = df["A"] < 3

# Combine masks (in this case we used "&") to achieve what a nested subset would look like
#  In the same step we add in our item assignment. Instructing pandas to create a new column in `df` and assign
#  the value "hello" to the rows in `df` where `q_mask` & `a_mask` overlap.
>>> df.loc[q_mask & a_mask, "new_col"] = "hello"

# Successful "propagation" of new values to the original dataframe
>>> df
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN
3  4  C     NaN
4  5  C     NaN

Por último, si alguna vez quisiéramos ver cómo se vería df_q, siempre podemos crear un subconjunto del marco de datos original usando nuestro q_mask

>>> df.loc[q_mask, :]
   A  B new_col
0  1  Q   hello
1  2  Q   hello
2  3  Q     NaN

Si bien esto no "propaga" necesariamente los cambios de df_q a df logramos el mismo resultado. La propagación real tendría que realizarse explícitamente y sería menos eficiente que simplemente trabajar con máscaras.

@CRiddler Gracias, ha sido de gran ayuda

¿Fue útil esta página
0 / 5 - 0 calificaciones