Acabo de actualizar mi Pandas de 0.11 a 0.13.0rc1. Ahora, la aplicación está sacando muchas advertencias nuevas. Uno de ellos como este:
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
Quiero saber qué significa exactamente? ¿Tengo que cambiar algo?
¿Cómo debo suspender la advertencia si insisto en utilizar quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
?
def _decode_stock_quote(list_of_150_stk_str):
"""decode the webpage and return dataframe"""
from cStringIO import StringIO
str_of_all = "".join(list_of_150_stk_str)
quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
quote_df['TClose'] = quote_df['TPrice']
quote_df['RT'] = 100 * (quote_df['TPrice']/quote_df['TPCLOSE'] - 1)
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
quote_df['TAmt'] = quote_df['TAmt']/TAMT_SCALE
quote_df['STK_ID'] = quote_df['STK'].str.slice(13,19)
quote_df['STK_Name'] = quote_df['STK'].str.slice(21,30)#.decode('gb2312')
quote_df['TDate'] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
return quote_df
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
E:\FinReporter\FM_EXT.py:450: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TAmt'] = quote_df['TAmt']/TAMT_SCALE
E:\FinReporter\FM_EXT.py:453: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TDate'] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
El SettingWithCopyWarning
fue creado para señalar asignaciones "encadenadas" potencialmente confusas, como las siguientes, que no siempre funcionan como se espera, particularmente cuando la primera selección devuelve una copia. [ver GH5390 y GH5597 para una discusión de fondo].
df[df['A'] > 2]['B'] = new_val # new_val not set in df
La advertencia ofrece una sugerencia para reescribir como sigue:
df.loc[df['A'] > 2, 'B'] = new_val
Sin embargo, esto no se ajusta a su uso, que es equivalente a
df = df[df['A'] > 2]
df['B'] = new_val
Mientras que está claro que no te importa que las escrituras vuelvan al marco original (ya que sobrescribiste la referencia a él), desafortunadamente este patrón no puede diferenciarse del primer ejemplo de asignación encadenada, de ahí la advertencia (falso positivo). El potencial de los falsos positivos se trata en los docs on indexing, si quieres leer más. Puede desactivar con seguridad esta nueva advertencia con la siguiente asignación.
pd.options.mode.chained_assignment = None # default='warn'
En general, el objetivo del SettingWithCopyWarning
es mostrar a los usuarios (y especialmente a los nuevos usuarios) que pueden estar operando en una copia y no en el original como creen. Hay falsos positivos (es decir, si sabes lo que estás haciendo podría estar bien). Una posibilidad es simplemente desactivar la advertencia (por defecto advertencia) como sugiere @Garrett.
Aquí hay otra opción:
In [1]: df = DataFrame(np.random.randn(5, 2), columns=list('AB'))
In [2]: dfa = df.ix[:, [1, 0]]
In [3]: dfa.is_copy
Out[3]: True
In [4]: dfa['A'] /= 2
/usr/local/bin/ipython:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
#!/usr/local/bin/python
Puede establecer el indicador is_copy
en False
, lo que desactivará efectivamente la comprobación, para ese objeto:
In [5]: dfa.is_copy = False
In [6]: dfa['A'] /= 2
Si se copia explícitamente, no se producirá ninguna otra advertencia:
In [7]: dfa = df.ix[:, [1, 0]].copy()
In [8]: dfa['A'] /= 2
El código que el OP muestra arriba, aunque legítimo, y probablemente algo que yo hago también, es técnicamente un caso para esta advertencia, y no un falso positivo. Otra forma de no tener la advertencia sería hacer la operación de selección a través de reindex
, por ejemplo
quote_df = quote_df.reindex(columns=['STK', ...])
O,
quote_df = quote_df.reindex(['STK', ...], axis=1) # v.0.21
Cuando vas y haces algo como esto
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
pandas.ix
en este caso devuelve un nuevo marco de datos independiente.
Cualquier valor que decida cambiar en este marco de datos, no cambiará el marco de datos original.
Esto es lo que pandas trata de advertirle.
.ix
es una mala ideaEl objeto .ix
intenta hacer más de una cosa, y para cualquiera que haya leído algo sobre código limpio, esto es un fuerte olor.
Dado este dataframe:
df = pd.DataFrame({"a": [1,2,3,4], "b": [1,1,2,2]})
Dos comportamientos:
dfcopy = df.ix[:,["a"]]
dfcopy.a.ix[0] = 2
Comportamiento uno: dfcopy
es ahora un marco de datos independiente. Si se cambia, no cambiará df
.
df.ix[0, "a"] = 3
Comportamiento dos: Esto cambia el marco de datos original.
.loc
en su lugarLos desarrolladores de pandas reconocieron que el objeto .ix
era bastante apestoso [especulativamente] y por ello crearon dos nuevos objetos que ayudan en la adhesión y asignación de datos. (El otro es .iloc
)
.loc
es más rápido, porque no intenta crear una copia de los datos.
.loc
está pensado para modificar el marco de datos existente in situ, lo que es más eficiente en cuanto a la memoria.
.loc
es predecible, tiene un comportamiento.
Lo que estás haciendo en tu ejemplo de código es cargar un archivo grande con muchas columnas, y luego modificarlo para que sea más pequeño.
La función pd.read_csv
puede ayudarte con mucho de esto y también hacer la carga del archivo mucho más rápida.
Así que en lugar de hacer esto
quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
Haz esto
columns = ['STK', 'TPrice', 'TPCLOSE', 'TOpen', 'THigh', 'TLow', 'TVol', 'TAmt', 'TDate', 'TTime']
df = pd.read_csv(StringIO(str_of_all), sep=',', usecols=[0,3,2,1,4,5,8,9,30,31])
df.columns = columns
Esto sólo leerá las columnas que le interesan, y las nombrará correctamente. No es necesario usar el malvado objeto .ix
para hacer cosas mágicas.