3.2. Orientación a columnas con Pandas

3.2. Orientación a columnas con Pandas#

Introducción#

En esta última parte del curso vamos a analizar el paradigma de orientación a columnas, como lo hace MariaDB ColumnStore y su relación con Pandas.

MariaDB Orientado a columnas y Series en Pandas#

Como vimos en todos los ejemplos anteriores, en Pandas los dataframes se comportan más similares a un almacenamiento por columnas que por filas, aunque cuando los emos visualmente, parece organizado por filas.

Por ejemplo, creabamos un dataframe como:

import pandas as pd

df_por_columnas = pd.DataFrame({
    'id_cliente': [1, 2, 3, 4, 5, 6, 7],
    'nombre': ['Susana', 'Oscar', 'Elena', 'Elena', 'Oscar', 'Elena', 'Lucila'],
    'apellido': ['Horia','Acol',  'Morado', 'Morado','Acol','Morado', 'Nave'],
    'ciudad': ['Neuquén', 'Rosario', 'Córdoba', 'Córdoba', 'Rosario', 'Córdoba', 'Plottier'],
    'producto': ['Palitos de la Selva',  'Chupetín Pico Dulce', 'Rocklets', 'Alfajor Jorgito', 'Tubby 4', 'Tubby 4', 'Tubby 4'],
    'precio': [100, 200, 150, 300, 120, 80, 230]
})

donde estamos creando cada columna de la forma: 'nombre': ['Susana', 'Oscar', 'Elena', 'Elena', 'Oscar', 'Elena', 'Lucila'] es decir es una columna. La unión de cada una de esas columnas, resulta en el dataframe. Incluso recordemos que cada columna de un dataframe es una serie.

También podriamos haber creado un dataframe como un conjunto de tuplas, y ahí sí, podemos ver una creación más orientada a filas que a columnas.

En cambio si lo hacemos por filas, sería como crear cada una de las tuplas:

#creamos una lista de tuplas
datos = [
    (1, 'Susana', 'Horia', 'Neuquén', 'Palitos de la Selva', 100),
    (2, 'Oscar', 'Acol', 'Rosario', 'Chupetín Pico Dulce', 200),
    (3, 'Elena', 'Morado', 'Córdoba', 'Rocklets', 150),
    (4, 'Elena', 'Morado', 'Córdoba', 'Alfajor Jorgito', 300),
    (5, 'Oscar', 'Acol', 'Rosario', 'Tubby 4', 120),
    (6, 'Elena', 'Morado', 'Córdoba', 'Tubby 4', 80),
    (7, 'Lucila', 'Nave', 'Plottier', 'Tubby 4', 230)
]

# Definimos las columnas por separado
columnas = ['id_cliente', 'nombre', 'apellido', 'ciudad', 'producto', 'precio']

# Creamos el DataFrame
df_por_filas = pd.DataFrame(datos, columns=columnas)

Visualmente el resultado es el mismo. Es decir, si mostramos por pantalla df_por_columnas y df_por_filas, vamos a ver el mismo resultado. Sin embargo la forma de crearlo fue diferente. Esta segunda forma simula una fila completa en cada dato (una tupla), en cambio en el primero creamos listas de cada columna.

Otra forma de simular una organización por columnas es utilizar un dict donde cada clave es el nombre de la columna, y los valores son los datos de la misma.

Haciendo un dict desde df_por_filas:

#dict column_store utilizando el df_por_filas creado previamente
#cada clave representa una columna independiente

dict_column_store = {
    'id_cliente': df_por_filas['id_cliente'].to_list(),
    'nombre': df_por_filas['nombre'].to_list(),
    'apellido': df_por_filas['apellido'].to_list(),
    'ciudad': df_por_filas['ciudad'].to_list(),
    'producto': df_por_filas['producto'].to_list(),
    'precio': df_por_filas['precio'].to_list()
}
#type(dict_column_store)
#dict_column_store.get('id_cliente')
dict_column_store
{'id_cliente': [1, 2, 3, 4, 5, 6, 7],
 'nombre': ['Susana', 'Oscar', 'Elena', 'Elena', 'Oscar', 'Elena', 'Lucila'],
 'apellido': ['Horia', 'Acol', 'Morado', 'Morado', 'Acol', 'Morado', 'Nave'],
 'ciudad': ['Neuquén',
  'Rosario',
  'Córdoba',
  'Córdoba',
  'Rosario',
  'Córdoba',
  'Plottier'],
 'producto': ['Palitos de la Selva',
  'Chupetín Pico Dulce',
  'Rocklets',
  'Alfajor Jorgito',
  'Tubby 4',
  'Tubby 4',
  'Tubby 4'],
 'precio': [100, 200, 150, 300, 120, 80, 230]}

Para simular un recorrido por columnas, podemos recorrer la estructura anterior (dict_column_store).

En el siguiente código, creamos un nuevo dict (total_por_producto) que va a tener:

  • una clave por cada nombre de producto diferente

  • por cada clave el precio total (que se va sumando)

Recorremos dict_column_store como si fueran columnas separadas:

# Creamos un diccionario para sumar precios por producto
total_por_producto = {}

# Recorremos las columnas simultáneamente
# el zip  permite usar dos o más listas (u otros iterables) y recorrerlas “en paralelo”, 
# devolviendo tuplas con un elemento de cada iterable en cada paso.

# en este caso recorremos el dict dict_column_store.get('producto') y cada valor se guarda en la variable producto
# y simultaneamente recorremos el dict dict_column_store.get('precio') y cada valor se guarda en la variable precio
for producto, precio in zip(dict_column_store.get('producto'), dict_column_store.get('precio')):
     print(f'El producto es : {producto}, y el precio  es: {precio}')
     
     if producto in total_por_producto:
         # si el producto esta en el dict total_por_producto, suma el precio
        total_por_producto[producto] += precio
     else:
        #sino crea un nuevo item del dict con el precio
        total_por_producto[producto] = precio

total_por_producto
El producto es : Palitos de la Selva, y el precio  es: 100
El producto es : Chupetín Pico Dulce, y el precio  es: 200
El producto es : Rocklets, y el precio  es: 150
El producto es : Alfajor Jorgito, y el precio  es: 300
El producto es : Tubby 4, y el precio  es: 120
El producto es : Tubby 4, y el precio  es: 80
El producto es : Tubby 4, y el precio  es: 230
{'Palitos de la Selva': 100,
 'Chupetín Pico Dulce': 200,
 'Rocklets': 150,
 'Alfajor Jorgito': 300,
 'Tubby 4': 430}

El código de arriba puede simular un motor orientado a columnas ya que:

  • se trabaja sobre columnas individuales

  • se puede operar sobre columnas sin cargar filas completas

  • se puede paralelizar operaciones por columna

  • se almacena y comprime cada columna por separado.