Index no se requiere que los objetos sean únicos; puede tener etiquetas de fila o columna duplicadas. Esto puede resultar un poco confuso al principio. Si está familiarizado con SQL, sabrá que las etiquetas de fila son similares a una clave principal en una tabla y nunca querrá duplicados en una tabla SQL. Pero una de las funciones de los pandas es limpiar datos desordenados del mundo real antes de que pasen a algún sistema posterior. Y los datos del mundo real tienen duplicados, incluso en campos que se supone que son únicos.
Index
En esta sección se describe cómo las etiquetas duplicadas cambian el comportamiento de ciertas operaciones y cómo evitar que surjan duplicados durante las operaciones, o detectarlas si lo hacen.
In [1]: import pandas as pd In [2]: import numpy as np
Algunos métodos de pandas (Series.reindex() por ejemplo) simplemente no funciona con duplicados presentes. La salida no se puede determinar, por lo que los pandas aumentan.
Series.reindex()
In [3]: s1 = pd.Series([0, 1, 2], index=["a", "b", "b"]) In [4]: s1.reindex(["a", "b", "c"]) --------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-4-18a38f6978fe> in <module> ----> 1 s1.reindex(["a", "b", "c"]) /pandas/pandas/core/series.py in reindex(self, index, **kwargs) 4313 ) 4314 def reindex(self, index=None, **kwargs): -> 4315 return super().reindex(index=index, **kwargs) 4316 4317 def drop( /pandas/pandas/core/generic.py in reindex(self, *args, **kwargs) 4804 4805 # perform the reindex on the axes -> 4806 return self._reindex_axes( 4807 axes, level, limit, tolerance, method, fill_value, copy 4808 ).__finalize__(self, method="reindex") /pandas/pandas/core/generic.py in _reindex_axes(self, axes, level, limit, tolerance, method, fill_value, copy) 4825 4826 axis = self._get_axis_number(a) -> 4827 obj = obj._reindex_with_indexers( 4828 {axis: [new_index, indexer]}, 4829 fill_value=fill_value, /pandas/pandas/core/generic.py in _reindex_with_indexers(self, reindexers, fill_value, copy, allow_dups) 4870 4871 # TODO: speed up on homogeneous DataFrame objects -> 4872 new_data = new_data.reindex_indexer( 4873 index, 4874 indexer, /pandas/pandas/core/internals/managers.py in reindex_indexer(self, new_axis, indexer, axis, fill_value, allow_dups, copy, consolidate, only_slice) 1299 # some axes don't allow reindexing with dups 1300 if not allow_dups: -> 1301 self.axes[axis]._can_reindex(indexer) 1302 1303 if axis >= self.ndim: /pandas/pandas/core/indexes/base.py in _can_reindex(self, indexer) 3474 # trying to reindex on an axis with duplicates 3475 if not self._index_as_unique and len(indexer): -> 3476 raise ValueError("cannot reindex from a duplicate axis") 3477 3478 def reindex(self, target, method=None, level=None, limit=None, tolerance=None): ValueError: cannot reindex from a duplicate axis
Otros métodos, como la indexación, pueden dar resultados muy sorprendentes. Normalmente, la indexación con un escalar reducir la dimensionalidad. Cortar un DataFrame con un escalar devolverá un Series. Cortar un Series con un escalar devolverá un escalar. Pero con los duplicados, este no es el caso.
DataFrame
Series
In [5]: df1 = pd.DataFrame([[0, 1, 2], [3, 4, 5]], columns=["A", "A", "B"]) In [6]: df1 Out[6]: A A B 0 0 1 2 1 3 4 5
Tenemos duplicados en las columnas. Si cortamos 'B', recuperamos un Series
'B'
In [7]: df1["B"] # a series Out[7]: 0 2 1 5 Name: B, dtype: int64
Pero cortando 'A' devuelve un DataFrame
'A'
In [8]: df1["A"] # a DataFrame Out[8]: A A 0 0 1 1 3 4
Esto también se aplica a las etiquetas de fila.
In [9]: df2 = pd.DataFrame({"A": [0, 1, 2]}, index=["a", "a", "b"]) In [10]: df2 Out[10]: A a 0 a 1 b 2 In [11]: df2.loc["b", "A"] # a scalar Out[11]: 2 In [12]: df2.loc["a", "A"] # a Series Out[12]: a 0 a 1 Name: A, dtype: int64
Puede comprobar si un Index (almacenar las etiquetas de fila o columna) es único con Index.is_unique:
Index.is_unique
In [13]: df2 Out[13]: A a 0 a 1 b 2 In [14]: df2.index.is_unique Out[14]: False In [15]: df2.columns.is_unique Out[15]: True
Nota
Comprobar si un índice es único es algo caro para grandes conjuntos de datos. pandas almacena en caché este resultado, por lo que volver a verificar el mismo índice es muy rápido.
Index.duplicated() devolverá un ndarray booleano que indica si una etiqueta se repite.
Index.duplicated()
In [16]: df2.index.duplicated() Out[16]: array([False, True, False])
Que se puede utilizar como filtro booleano para eliminar filas duplicadas.
In [17]: df2.loc[~df2.index.duplicated(), :] Out[17]: A a 0 b 2
Si necesita lógica adicional para manejar etiquetas duplicadas, en lugar de simplemente descartar las repeticiones, use groupby() en el índice es un truco común. Por ejemplo, resolveremos los duplicados tomando el promedio de todas las filas con la misma etiqueta.
groupby()
In [18]: df2.groupby(level=0).mean() Out[18]: A a 0.5 b 2.0
Nuevo en la versión 1.2.0.
Como se señaló anteriormente, el manejo de duplicados es una característica importante al leer datos sin procesar. Dicho esto, es posible que desee evitar la introducción de duplicados como parte de una canalización de procesamiento de datos (de métodos como pandas.concat(), rename(), etc.). Ambos Series y DataFrame rechazar duplicar etiquetas llamando .set_flags(allows_duplicate_labels=False). (el valor predeterminado es permitirlos). Si hay etiquetas duplicadas, se generará una excepción.
pandas.concat()
rename()
.set_flags(allows_duplicate_labels=False)
In [19]: pd.Series([0, 1, 2], index=["a", "b", "b"]).set_flags(allows_duplicate_labels=False) --------------------------------------------------------------------------- DuplicateLabelError Traceback (most recent call last) <ipython-input-19-11af4ee9738e> in <module> ----> 1 pd.Series([0, 1, 2], index=["a", "b", "b"]).set_flags(allows_duplicate_labels=False) /pandas/pandas/core/generic.py in set_flags(self, copy, allows_duplicate_labels) 332 df = self.copy(deep=copy) 333 if allows_duplicate_labels is not None: --> 334 df.flags["allows_duplicate_labels"] = allows_duplicate_labels 335 return df 336 /pandas/pandas/core/flags.py in __setitem__(self, key, value) 103 if key not in self._keys: 104 raise ValueError(f"Unknown flag {key}. Must be one of {self._keys}") --> 105 setattr(self, key, value) 106 107 def __repr__(self): /pandas/pandas/core/flags.py in allows_duplicate_labels(self, value) 90 if not value: 91 for ax in obj.axes: ---> 92 ax._maybe_check_unique() 93 94 self._allows_duplicate_labels = value /pandas/pandas/core/indexes/base.py in _maybe_check_unique(self) 469 msg += f"n{duplicates}" 470 --> 471 raise DuplicateLabelError(msg) 472 473 @final DuplicateLabelError: Index has duplicates. positions label b [1, 2]
Esto se aplica a las etiquetas de fila y columna para un DataFrame
In [20]: pd.DataFrame([[0, 1, 2], [3, 4, 5]], columns=["A", "B", "C"],).set_flags( ....: allows_duplicate_labels=False ....: ) ....: Out[20]: A B C 0 0 1 2 1 3 4 5
Este atributo se puede comprobar o configurar con allows_duplicate_labels, que indica si ese objeto puede tener etiquetas duplicadas.
allows_duplicate_labels
In [21]: df = pd.DataFrame({"A": [0, 1, 2, 3]}, index=["x", "y", "X", "Y"]).set_flags( ....: allows_duplicate_labels=False ....: ) ....: In [22]: df Out[22]: A x 0 y 1 X 2 Y 3 In [23]: df.flags.allows_duplicate_labels Out[23]: False
DataFrame.set_flags() se puede utilizar para devolver un nuevo DataFrame con atributos como allows_duplicate_labels establecido en algún valor
DataFrame.set_flags()
In [24]: df2 = df.set_flags(allows_duplicate_labels=True) In [25]: df2.flags.allows_duplicate_labels Out[25]: True
El nuevo DataFrame devuelto es una vista de los mismos datos que el antiguo DataFrame. O la propiedad se puede establecer directamente en el mismo objeto
In [26]: df2.flags.allows_duplicate_labels = False In [27]: df2.flags.allows_duplicate_labels Out[27]: False
Al procesar datos sin procesar y desordenados, es posible que inicialmente lea los datos desordenados (que potencialmente tienen etiquetas duplicadas), deduplicar y luego no permitir duplicados en el futuro, para asegurarse de que su canal de datos no introduzca duplicados.
>>> raw = pd.read_csv("...") >>> deduplicated = raw.groupby(level=0).first() # remove duplicates >>> deduplicated.flags.allows_duplicate_labels = False # disallow going forward
Configuración allows_duplicate_labels=True en un Series o DataFrame con etiquetas duplicadas o realizando una operación que introduce etiquetas duplicadas en un Series o DataFrame que no permite duplicados generará un errors.DuplicateLabelError.
allows_duplicate_labels=True
errors.DuplicateLabelError
In [28]: df.rename(str.upper) --------------------------------------------------------------------------- DuplicateLabelError Traceback (most recent call last) <ipython-input-28-17c8fb0b7c7f> in <module> ----> 1 df.rename(str.upper) /pandas/pandas/util/_decorators.py in wrapper(*args, **kwargs) 310 @wraps(func) 311 def wrapper(*args, **kwargs) -> Callable[..., Any]: --> 312 return func(*args, **kwargs) 313 314 kind = inspect.Parameter.POSITIONAL_OR_KEYWORD /pandas/pandas/core/frame.py in rename(self, mapper, index, columns, axis, copy, inplace, level, errors) 4436 4 3 6 4437 """ -> 4438 return super().rename( 4439 mapper=mapper, 4440 index=index, /pandas/pandas/core/generic.py in rename(self, mapper, index, columns, axis, copy, inplace, level, errors) 1060 return None 1061 else: -> 1062 return result.__finalize__(self, method="rename") 1063 1064 @rewrite_axis_style_signature("mapper", [("copy", True), ("inplace", False)]) /pandas/pandas/core/generic.py in __finalize__(self, other, method, **kwargs) 5428 self.attrs[name] = other.attrs[name] 5429 -> 5430 self.flags.allows_duplicate_labels = other.flags.allows_duplicate_labels 5431 # For subclasses using _metadata. 5432 for name in set(self._metadata) & set(other._metadata): /pandas/pandas/core/flags.py in allows_duplicate_labels(self, value) 90 if not value: 91 for ax in obj.axes: ---> 92 ax._maybe_check_unique() 93 94 self._allows_duplicate_labels = value /pandas/pandas/core/indexes/base.py in _maybe_check_unique(self) 469 msg += f"n{duplicates}" 470 --> 471 raise DuplicateLabelError(msg) 472 473 @final DuplicateLabelError: Index has duplicates. positions label X [0, 2] Y [1, 3]
Este mensaje de error contiene las etiquetas que están duplicadas y las posiciones numéricas de todos los duplicados (incluido el “original”) en el Series o DataFrame
En general, rechazar los duplicados es “pegajoso”. Se conserva mediante operaciones.
In [29]: s1 = pd.Series(0, index=["a", "b"]).set_flags(allows_duplicate_labels=False) In [30]: s1 Out[30]: a 0 b 0 dtype: int64 In [31]: s1.head().rename({"a": "b"}) --------------------------------------------------------------------------- DuplicateLabelError Traceback (most recent call last) <ipython-input-31-8f09bda3af1a> in <module> ----> 1 s1.head().rename({"a": "b"}) /pandas/pandas/core/series.py in rename(self, index, axis, copy, inplace, level, errors) 4271 """ 4272 if callable(index) or is_dict_like(index): -> 4273 return super().rename( 4274 index, copy=copy, inplace=inplace, level=level, errors=errors 4275 ) /pandas/pandas/core/generic.py in rename(self, mapper, index, columns, axis, copy, inplace, level, errors) 1060 return None 1061 else: -> 1062 return result.__finalize__(self, method="rename") 1063 1064 @rewrite_axis_style_signature("mapper", [("copy", True), ("inplace", False)]) /pandas/pandas/core/generic.py in __finalize__(self, other, method, **kwargs) 5428 self.attrs[name] = other.attrs[name] 5429 -> 5430 self.flags.allows_duplicate_labels = other.flags.allows_duplicate_labels 5431 # For subclasses using _metadata. 5432 for name in set(self._metadata) & set(other._metadata): /pandas/pandas/core/flags.py in allows_duplicate_labels(self, value) 90 if not value: 91 for ax in obj.axes: ---> 92 ax._maybe_check_unique() 93 94 self._allows_duplicate_labels = value /pandas/pandas/core/indexes/base.py in _maybe_check_unique(self) 469 msg += f"n{duplicates}" 470 --> 471 raise DuplicateLabelError(msg) 472 473 @final DuplicateLabelError: Index has duplicates. positions label b [0, 1]
Advertencia
Esta es una característica experimental. Actualmente, muchos métodos no logran propagar la allows_duplicate_labels valor. En versiones futuras, se espera que todos los métodos que toman o devuelven uno o más objetos DataFrame o Series se propagarán allows_duplicate_labels.
Trabajar con datos faltantes Datos categóricos