Semana 12: Pandas 1#

Juego emparejados numpy y matplotlib

12.1. Definición#

Python Data Analysis Library.

Pandas es una de las librerías de Python más populares en ciencia de datos. Proporciona estructuras de datos de alto nivel y funciones que están diseñadas para que el trabajo con datos estructurados o tabulares sea rápido y fácil. En particular, Pandas proporciona funcionalidades que facilitan la indexación, segmentación, el uso de agregaciones y mucho más. Dado que la manipulación y limpieza de datos son habilidades importantes en la ciencia de datos, esta será una herramienta imprescindible.

Pandas está construida sobre Numpy, por lo que podemos usar los métodos y funcionalidades de los arreglos de numpy que estudiamos en las sesiones anteriores. Además, los objetos de pandas van a ser las estructuras de datos con las cuales vamos a alimentar los algoritmos de Machine Learning, por lo que va a ser fundamental tener un buen conocimiento de estos objetos.

# importaciones
import numpy as np
import pandas as pd

12.2. Series de pandas#

Una serie de pandas es un arreglo unidimensional de datos indexados

Podemos crear una serie a partir de una lista o arreglo mediante el constructor Series

# datos unidimensionales
lista = [3, -1, 7, 6, 22]

# indices
indices = ["a", "Fabio", 2, "Mariana", "E"]

# creacion de la serie
serie = pd.Series(data=lista, index=indices, dtype="int8")
serie
a           3
Fabio      -1
2           7
Mariana     6
E          22
dtype: int8

Podemos acceder al tipo de dato, valores e índices de la serie con los atributos dtype, values e index, respectivamente

# tipo de dato en la serie
serie.dtype
dtype('int8')
# valores de la serie
serie.values
array([ 3, -1,  7,  6, 22], dtype=int8)
# indices de la serie
serie.index
Index(['a', 'Fabio', 2, 'Mariana', 'E'], dtype='object')

12.3. Dataframe de pandas#

La siguiente estructura fundamental en Pandas es el Dataframe. Si una serie es un análogo de un arreglo unidimensional con índices flexibles, un DataFrame es un análogo de un arreglo bidimensional con índices de fila y columna flexibles

s = np.array([[1, 2, 3],[4,5,6]])
s[0,2]
3

Podemos construir un dataframe con el constructor DataFrame

# Construir un DataFrame a partir de un diccionario

#Declarando el diccionario de nombre "data"
data = {
    "Pais": ["Suiza", "Australia", "Canada"],
    "Capital": ["Berna", "Canberra", "Ottawa"],
    "Pob": [8.6, 25.7, 38]
}

indices = ["A", "B","C"]

#Creando el DataFrame, al que estoy nombrando como "tabla"
tabla = pd.DataFrame(data = data, index= indices)
tabla
Pais Capital Pob
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0
#Crear DataFrame con un arreglo en dos dimensiones

# arreglo bidimensional 3x3
data = [["Suiza", "Berna", 8.6],
        ["Australia", "Canberra", 25.7],
        ["Canada", "Ottawa", 38]]

# indices de columna
columnas = ["Pais", "Capital", "Pob"]

#indices filas
indices = ["A", "B","C"]
df = pd.DataFrame(data=data, columns=columnas, index=indices)
df
Pais Capital Pob
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0
# indices de fila
indices = ["PAIS A", "PAIS B", "PAIS C"]
# creacion del DataFrame
df = pd.DataFrame(data=data, index=indices, columns=columnas)
df
Pais Capital Pob
PAIS A Suiza Berna 8.6
PAIS B Australia Canberra 25.7
PAIS C Canada Ottawa 38.0

Al igual que con la serie, podemos acceder a cada uno de los elementos que componen el DataFrame. En este caso, tenemos un objeto tipo index tanto para las filas como para las columnas

# tipos de dato
df.dtypes
Pais        object
Capital     object
Pob        float64
dtype: object
# valores
df.values
array([['Suiza', 'Berna', 8.6],
       ['Australia', 'Canberra', 25.7],
       ['Canada', 'Ottawa', 38.0]], dtype=object)
# indices de fila
df.index
Index(['PAIS A', 'PAIS B', 'PAIS C'], dtype='object')
# indices de columnas
df.columns
Index(['Pais', 'Capital', 'Pob'], dtype='object')

12.3.1. Índices**#

Si no especificamos los indices de fila o columna, Pandas los asigna los de forma automática. Podemos redefinirlos de dos formas:

Modificando directamente los atributos index y columns

df
Pais Capital Pob
PAIS A Suiza Berna 8.6
PAIS B Australia Canberra 25.7
PAIS C Canada Ottawa 38.0
# modificando los indices
df.index = ["a", "b", "c"]
df
Pais Capital Pob
a Suiza Berna 8.6
b Australia Canberra 25.7
c Canada Ottawa 38.0
# modificando las columnas
df.columns = ["pais", "capital", "poblacion"]
df
pais capital poblacion
a Suiza Berna 8.6
b Australia Canberra 25.7
c Canada Ottawa 38.0

o mediante el método set_axis, indicando con el kwarg axis el eje a utilizar

# modificando los indices (axis 0)
df = df.set_axis(labels=["A", "B", "C"], axis=0)
df
pais capital poblacion
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0
# modificando las columnas (axis 1)
df = df.set_axis(labels=["Pais", "Capital", "Poblacion"], axis=1)
df
Pais Capital Poblacion
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0

Podemos modificar un índice de fila o columna individualmente mediante el método rename, indicando las columnas e indices que queremos modificar mediante un mapeo:

# modificar el nombre de una columna
df.rename(columns={"Pais": "pais"})
pais Capital Poblacion
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0
# modificar el nombre de una fila
df.rename(index={"A": "a"})
Pais Capital Poblacion
a Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0

El método reset_index nos devuelve a la configuración inicial de índices por defecto

df
Pais Capital Poblacion
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0
# reinicializando los indices
df.reset_index()
index Pais Capital Poblacion
0 A Suiza Berna 8.6
1 B Australia Canberra 25.7
2 C Canada Ottawa 38.0
df.reset_index(drop=True)
Pais Capital Poblacion
0 Suiza Berna 8.6
1 Australia Canberra 25.7
2 Canada Ottawa 38.0

12.3.2. Valores#

Con el método replace podemos modificar los valores del DataFrame, de foma individual, o múltiples elementos mediante un mapeo:

df
Pais Capital Poblacion
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0
# modificando un valor individual
df.replace("Suiza", "suiza")
Pais Capital Poblacion
A suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0
# modificando multiples valores
df.replace({"Suiza": "suiza", 38.0: 38.5, 8.6:8.67})
Pais Capital Poblacion
A suiza Berna 8.67
B Australia Canberra 25.70
C Canada Ottawa 38.50

Con sort_values podemos ordenar valores en un DataFrame, indicando la columna que queremos ordenar mediante el kwarg by

df
Pais Capital Poblacion
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0
# ordenando ascendentemente por nombre de columna
df.sort_values(by="Poblacion")
Pais Capital Poblacion
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0
df.sort_values(by="Pais")
Pais Capital Poblacion
B Australia Canberra 25.7
C Canada Ottawa 38.0
A Suiza Berna 8.6
df
Pais Capital Poblacion
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0
df.sort_values(by="Poblacion", ascending=False)
Pais Capital Poblacion
C Canada Ottawa 38.0
B Australia Canberra 25.7
A Suiza Berna 8.6
df.sort_values(by=["Pais", "Poblacion"], ascending=False)
Pais Capital Poblacion
A Suiza Berna 8.6
C Canada Ottawa 38.0
B Australia Canberra 25.7
df
Pais Capital Poblacion
A Suiza Berna 8.6
B Australia Canberra 25.7
C Canada Ottawa 38.0

12.4. Cargando conjuntos de datos#

La mayoría de las veces trabajaremos con datos existentes que necesitaremos cargar a un DataFrame. La función que debemos utilizar para esta tarea dependerá del formato en el que se ha generado el conjunto de datos.

El formato más común es el CSV, para el cual pandas nos proporciona la funcion read_csv.

# direccion del .csv
fname = "/content/stars.csv"

# cargar el conjunto de datos a un dataframe
df = pd.read_csv(fname)
df
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Input In [36], in <cell line: 5>()
      2 fname = "/content/stars.csv"
      4 # cargar el conjunto de datos a un dataframe
----> 5 df = pd.read_csv(fname)
      6 df

File ~/anaconda3/lib/python3.9/site-packages/pandas/util/_decorators.py:311, in deprecate_nonkeyword_arguments.<locals>.decorate.<locals>.wrapper(*args, **kwargs)
    305 if len(args) > num_allow_args:
    306     warnings.warn(
    307         msg.format(arguments=arguments),
    308         FutureWarning,
    309         stacklevel=stacklevel,
    310     )
--> 311 return func(*args, **kwargs)

File ~/anaconda3/lib/python3.9/site-packages/pandas/io/parsers/readers.py:680, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, error_bad_lines, warn_bad_lines, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options)
    665 kwds_defaults = _refine_defaults_read(
    666     dialect,
    667     delimiter,
   (...)
    676     defaults={"delimiter": ","},
    677 )
    678 kwds.update(kwds_defaults)
--> 680 return _read(filepath_or_buffer, kwds)

File ~/anaconda3/lib/python3.9/site-packages/pandas/io/parsers/readers.py:575, in _read(filepath_or_buffer, kwds)
    572 _validate_names(kwds.get("names", None))
    574 # Create the parser.
--> 575 parser = TextFileReader(filepath_or_buffer, **kwds)
    577 if chunksize or iterator:
    578     return parser

File ~/anaconda3/lib/python3.9/site-packages/pandas/io/parsers/readers.py:933, in TextFileReader.__init__(self, f, engine, **kwds)
    930     self.options["has_index_names"] = kwds["has_index_names"]
    932 self.handles: IOHandles | None = None
--> 933 self._engine = self._make_engine(f, self.engine)

File ~/anaconda3/lib/python3.9/site-packages/pandas/io/parsers/readers.py:1217, in TextFileReader._make_engine(self, f, engine)
   1213     mode = "rb"
   1214 # error: No overload variant of "get_handle" matches argument types
   1215 # "Union[str, PathLike[str], ReadCsvBuffer[bytes], ReadCsvBuffer[str]]"
   1216 # , "str", "bool", "Any", "Any", "Any", "Any", "Any"
-> 1217 self.handles = get_handle(  # type: ignore[call-overload]
   1218     f,
   1219     mode,
   1220     encoding=self.options.get("encoding", None),
   1221     compression=self.options.get("compression", None),
   1222     memory_map=self.options.get("memory_map", False),
   1223     is_text=is_text,
   1224     errors=self.options.get("encoding_errors", "strict"),
   1225     storage_options=self.options.get("storage_options", None),
   1226 )
   1227 assert self.handles is not None
   1228 f = self.handles.handle

File ~/anaconda3/lib/python3.9/site-packages/pandas/io/common.py:789, in get_handle(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)
    784 elif isinstance(handle, str):
    785     # Check whether the filename is to be opened in binary mode.
    786     # Binary mode does not support 'encoding' and 'newline'.
    787     if ioargs.encoding and "b" not in ioargs.mode:
    788         # Encoding
--> 789         handle = open(
    790             handle,
    791             ioargs.mode,
    792             encoding=ioargs.encoding,
    793             errors=errors,
    794             newline="",
    795         )
    796     else:
    797         # Binary mode
    798         handle = open(handle, ioargs.mode)

FileNotFoundError: [Errno 2] No such file or directory: '/content/stars.csv'

Podemos definir cuál columna utilizar como índice mediante el kwarg index_col

# definiendo el indice
df1 = pd.read_csv(fname, index_col=0)
df1
AR [hms] DEC [dms] mu-ra*cos(dec) [mas/yr] mu-dec [mas/yr] plx [mas] radvel [km/s]
ID
* tau Cyg 21 14 47.493 +38 02 43.144 196.990 410.280 49.1600 -20.900
* sig Cyg 21 17 24.952 +39 23 40.853 -0.130 -3.580 1.1300 -5.300
* 6 Lac 22 30 29.26 +43 07 24.156 -1.980 -5.360 1.9000 -8.700
* 11 Lac 22 40 30.859 +44 16 34.704 93.830 10.710 9.8000 -11.530
* psi And 23 46 2.047 +46 25 12.986 8.446 -7.143 1.6352 -23.163
... ... ... ... ... ... ...
* 32 Eri A 03 54 17.502 -02 57 17.04 26.330 0.079 9.4562 26.300
* del Ser A 15 34 48.145 +10 32 19.88 -73.952 5.748 10.8056 -20.820
* phi Per 01 43 39.638 +50 41 19.433 24.590 -14.010 4.5400 0.800
* alf Eri 01 37 42.845 -57 14 12.31 87.000 -38.240 23.3900 18.600
* 78 Peg A 23 43 59.485 +29 21 41.24 70.440 -41.070 14.2913 -8.440

1597 rows × 6 columns

df = pd.read_excel('/content/DatosGAIA.xlsx')
df
RA (grados) DEC (grados) Paralaje (mas) Mov propio RA (mas/año) Mov propio DEC (mas/año) G (mag) RP (mag) Radio (Rsun) Temperatura (K) Luminosidad (Lsun)
0 103.447529 56.022025 0.582790 6.040461 5.055291 15.770850 15.275072 1.024730 5807.0 1.075774
1 105.187856 56.267982 1.385686 22.897881 -9.885237 13.252875 12.741846 1.388711 5779.0 1.937890
2 103.424758 56.450903 0.314035 -4.521304 -5.547879 19.861720 18.830698 NaN NaN NaN
3 105.049751 56.508777 1.939951 -2.566329 1.834169 20.511896 19.258364 NaN NaN NaN
4 103.352525 56.395144 0.747108 5.426702 -3.468109 14.344414 13.870882 1.507958 5867.0 2.427377
... ... ... ... ... ... ... ... ... ... ...
14204 103.317124 56.619457 0.635295 1.347417 -1.942009 18.441875 17.846388 NaN NaN NaN
14205 105.467759 56.261516 -5.242704 -0.084027 -2.525625 20.720303 NaN NaN NaN NaN
14206 105.200935 56.399065 -0.339148 13.110889 -16.434445 19.104810 18.310467 NaN NaN NaN
14207 103.574689 56.431876 1.992706 2.955513 0.105719 20.382227 19.382633 NaN NaN NaN
14208 103.163783 56.889418 0.438020 0.700475 -4.456369 18.526974 17.691576 NaN NaN NaN

14209 rows × 10 columns

Para una mejor visualización del DataFrame podemos utilizar el método head, que por defecto nos muestra únicamente las primeras cinco filas del dataframe:

# primeras cinco filas
df.head(5)
RA (grados) DEC (grados) Paralaje (mas) Mov propio RA (mas/año) Mov propio DEC (mas/año) G (mag) RP (mag) Radio (Rsun) Temperatura (K) Luminosidad (Lsun)
0 103.447529 56.022025 0.582790 6.040461 5.055291 15.770850 15.275072 1.024730 5807.0 1.075774
1 105.187856 56.267982 1.385686 22.897881 -9.885237 13.252875 12.741846 1.388711 5779.0 1.937890
2 103.424758 56.450903 0.314035 -4.521304 -5.547879 19.861720 18.830698 NaN NaN NaN
3 105.049751 56.508777 1.939951 -2.566329 1.834169 20.511896 19.258364 NaN NaN NaN
4 103.352525 56.395144 0.747108 5.426702 -3.468109 14.344414 13.870882 1.507958 5867.0 2.427377

usamos tail para ver las últimas filas:

# ultimas cinco filas
df.tail(5)
RA (grados) DEC (grados) Paralaje (mas) Mov propio RA (mas/año) Mov propio DEC (mas/año) G (mag) RP (mag) Radio (Rsun) Temperatura (K) Luminosidad (Lsun)
14204 103.317124 56.619457 0.635295 1.347417 -1.942009 18.441875 17.846388 NaN NaN NaN
14205 105.467759 56.261516 -5.242704 -0.084027 -2.525625 20.720303 NaN NaN NaN NaN
14206 105.200935 56.399065 -0.339148 13.110889 -16.434445 19.104810 18.310467 NaN NaN NaN
14207 103.574689 56.431876 1.992706 2.955513 0.105719 20.382227 19.382633 NaN NaN NaN
14208 103.163783 56.889418 0.438020 0.700475 -4.456369 18.526974 17.691576 NaN NaN NaN

O el método sample para visualizar registros de forma aleatoria:

# cinco filas aleatorias
df.sample(5)
RA (grados) DEC (grados) Paralaje (mas) Mov propio RA (mas/año) Mov propio DEC (mas/año) G (mag) RP (mag) Radio (Rsun) Temperatura (K) Luminosidad (Lsun)
7750 105.327821 56.518746 NaN NaN NaN 18.739670 16.681902 NaN NaN NaN
9065 102.576074 55.965205 0.075456 3.692690 2.044376 20.366179 19.257305 NaN NaN NaN
5887 102.560006 55.949655 0.655650 9.514146 -2.790923 19.198140 18.070460 NaN NaN NaN
3350 102.219087 55.966901 1.134902 -0.986667 -6.647907 20.565125 19.239555 NaN NaN NaN
1405 103.523678 56.520547 -0.828373 -0.994143 -3.795978 20.281048 19.173010 NaN NaN NaN

Para conocer las dimensiones del DataFrame utilizamos el atributo shape

# forma del arreglo subyacente
df.shape
(14209, 10)
df.dtypes
RA (grados)                 float64
DEC (grados)                float64
Paralaje (mas)              float64
Mov propio RA (mas/año)     float64
Mov propio DEC (mas/año)    float64
G (mag)                     float64
RP (mag)                    float64
Radio (Rsun)                float64
Temperatura (K)             float64
Luminosidad (Lsun)          float64
dtype: object
df.index
RangeIndex(start=0, stop=14209, step=1)
df.columns
Index(['RA (grados)', 'DEC (grados)', 'Paralaje (mas)',
       'Mov propio RA (mas/año)', 'Mov propio DEC (mas/año)', 'G (mag)',
       'RP (mag)', 'Radio (Rsun)', 'Temperatura (K)', 'Luminosidad (Lsun)'],
      dtype='object')

El método info nos da un resumen general del DataFrame

# descripcion general del dataframe
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14209 entries, 0 to 14208
Data columns (total 10 columns):
 #   Column                    Non-Null Count  Dtype  
---  ------                    --------------  -----  
 0   RA (grados)               14209 non-null  float64
 1   DEC (grados)              14209 non-null  float64
 2   Paralaje (mas)            12715 non-null  float64
 3   Mov propio RA (mas/año)   12715 non-null  float64
 4   Mov propio DEC (mas/año)  12715 non-null  float64
 5   G (mag)                   14209 non-null  float64
 6   RP (mag)                  13556 non-null  float64
 7   Radio (Rsun)              1860 non-null   float64
 8   Temperatura (K)           2912 non-null   float64
 9   Luminosidad (Lsun)        1860 non-null   float64
dtypes: float64(10)
memory usage: 1.1 MB
df.isna().sum()
RA (grados)                     0
DEC (grados)                    0
Paralaje (mas)               1494
Mov propio RA (mas/año)      1494
Mov propio DEC (mas/año)     1494
G (mag)                         0
RP (mag)                      653
Radio (Rsun)                12349
Temperatura (K)             11297
Luminosidad (Lsun)          12349
dtype: int64
import matplotlib.pyplot as plt

x=df1['mu-dec [mas/yr]']
y=df1['plx [mas]']

fig, ax = plt.subplots()

ax.plot(x,y,'r*')
ax.set_xlabel('mu-dec [mas/yr]')
ax.set_ylabel('plx [mas]')
Text(0, 0.5, 'plx [mas]')
_images/ee40c4571245ad7922704816edcac6071c847ad64c25ea14a63a9b09d7a0235f.png