Data Quality
Tags: python, dataengineering, data, codequality
Published: 2023-11-12 23:53:23

# Introducción

En el entorno digital actual, la cantidad de datos disponibles es abrumadora. Sin embargo, la verdadera piedra angular para tomar decisiones informadas reside en la calidad de estos datos. En este artículo, exploraremos la importancia crucial de la calidad de datos, analizando los desafíos inherentes que enfrentan las organizaciones en la gestión de la información. Aunque a menudo pasada por alto, la calidad de datos desempeña un papel fundamental en la confiabilidad y utilidad de la información que sustenta nuestras decisiones estratégicas.

----

# Que es Data quality?

Data quality ó calidad de los datos mide qué tan bien un conjunto de datos cumple con los criterios de accuracy, completeness, validity, consistency, uniqueness, timeliness y fitness para el propósito, y es fundamental para todas las iniciativas de gobernanza de datos dentro de una organización. Los estándares de calidad de los datos garantizan que las empresas tomen decisiones basadas en datos para alcanzar sus objetivos comerciales.

> source: [IBM](https://www.ibm.com/topics/data-quality)

![DataCamp cheat sheet](https://images.datacamp.com/image/upload/v1678809340/Data_Quality_Dimensions_33eb1d29b9.png)
source: [DataCamp cheat sheet](https://www.datacamp.com/cheat-sheet/data-quality-dimensions-cheat-sheet)

# Data quality dimensions

En la siguiente tabla destaca los distintos dominios de calidad de datos, desde la precisión hasta la aptitud, proporcionando una guía esencial para evaluar y mejorar la robustez de los conjuntos de datos.


| **Dimensión**                   | **Descripción**                                                                                                                                                                                                             
|---------------------------------|----------------------
| **🎯 Precisión (Accuracy)**      | Exactitud de los datos, es decir, qué tan cerca están de la realidad o la verdad. Datos precisos son aquellos que reflejan con precisión la información que intentan representar.                                           
| **🧩 Integridad (Completeness)** | Mide la totalidad de los datos. Un conjunto de datos completo es aquel que no tiene valores faltantes o huecos significativos. La integridad de los datos es crucial para obtener una visión completa y precisa.            |
| **✅ Validez (Validity)**        | Indica si los datos están en conformidad con las reglas y estándares definidos. Los datos válidos cumplen con las restricciones y criterios establecidos para un conjunto de datos específico.                              
| **🔄 Coherencia (Consistency)**  | Se refiere a la uniformidad de los datos a lo largo del tiempo y entre diferentes conjuntos de datos. Datos coherentes no presentan contradicciones o discrepancias cuando se comparan entre sí.                            
| **📇 Unicidad (Uniqueness)**     | Evalúa si no hay duplicados en los datos. Los datos únicos garantizan que cada entidad o elemento esté representado solo una vez en un conjunto de datos.                                                                   
| **⌛ Oportunidad (Timeliness)**  | Se refiere a la actualidad de los datos. La información oportuna es aquella que está disponible cuando se necesita, sin demoras innecesarias.                                                                               
| **🏋️ Aptitud (Fitness)**        | Este aspecto evalúa la relevancia y utilidad de los datos para el propósito previsto. Los datos deben ser adecuados y aplicables a los objetivos específicos de la organización o del análisis que se esté llevando a cabo. |


# Data quality dimensions- caso de uso

A continuación colocamos un ejemplo en el que se puede observar algunos problemas que tiene un caso de uso basado en eccommerce


| **ID Transacción** | **ID Cliente** | **Producto**             | **Cantidad** | **Precio Unitario** | **Total** |
|-------|-------|-------|-------|-------|-------|
| ⚪ 1                | 10234          | Laptop HP                | 1            | \$800               | \$800     |
| 🟣 2                |                | Auriculares Inalámbricos | 2            | \$50                | \$100     
| 🔵 3                | 10235          | Teléfono Inteligente     | -1           | \$1000              | -\$1000   |
| 🟢 4                | 10236          | Ratón Inalámbrico        | 3            | \$30                | \$90      |
| 🟢 4                | 10237          | Teclado Inalámbrico      | 2            | \$40                | \$80      |

1.  **🟣 Fila 2 (Completeness):** La fila 2 no cumple con la integridad de los datos (Completeness) ya que falta el ID del cliente.
    La información sobre el cliente está incompleta, lo que dificulta el seguimiento de la transacción hasta un cliente específico.

2.  **🔵Fila 3 (Accuracy y Consistency):** La fila 3 presenta problemas de exactitud (Accuracy) y coherencia (Consistency).
    La cantidad de productos es negativa, lo cual es inexacto y va en contra de la consistencia esperada en un conjunto de datos de transacciones.

3.  **🟢Fila 4 (Unicidad):** La introducción de una segunda fila con el mismo ID de transacción (ID Transacción = 4) viola el principio de unicidad.
    Cada transacción debe tener un identificador único, y la presencia de dos filas con el mismo ID Transacción crea duplicados, afectando la unicidad de las transacciones.\

-------------------------------------------------------------

# Python Frameworks

Los siguientes son algunas de las implementaciones realizadas en python para poder realizar validaciones de la calidad de los datos


| **Framework** | **Descripción**       | 
|---------------|-----------------------|                                                                                                                                                                                                                                                                                             
| [**Great Expectations**](https://github.com/great-expectations/great_expectations) | Great Expectations es una biblioteca open-source para la validación de datos. Permite definir, documentar y validar expectativas sobre los datos, garantizando la calidad y consistencia en proyectos de ciencia de datos y análisis.           |
| [**Pandera**](https://github.com/unionai-oss/pandera)                              | Pandera es una biblioteca de validación de datos para estructuras de datos en Python, especialmente diseñada para trabajar con DataFrames de pandas. Permite definir esquemas y reglas de validación para asegurar la conformidad de los datos. |
| [**Dora**](https://github.com/NathanEpstein/Dora)                                  | Dora es una libreria python diseñada para automatizar exploración de datos y realizar analisis de datos exploratorios  |     
                       

Anlicemos algunas de las metricas que se pueden observar en sus repositorios de github, teniendo en cuenta que las metricas fueron obtenidas el `2023-11-12`


| Metricas                        | Great Expectations | Pandera | Dora               |
|---------------------------------|--------------------|---------|--------------------|
| 👥 Colaboradores                | 399                | 109     | 106                |
| ⚠️ Issues abiertos              | 112                | 273     | 1                  |
| 🟢 Issues Cerrados              | 1642               | 419     | 7                  |
| ⭐ Stars                        | 9000               | 2700    | 623                |
| 📺 Watching                     | 78                 | 17      | 42                 |
| 🔎 Forks                        | 1400               | 226     | 63                 |
| 📬 Open PR                      | 43                 | 19      | 0                  |
| 🐍 Python version               | >=3.8              | >=3.7   | No especificada    |
| 📄 Nro. Versiones               | 233                | 76      | 3                  |
| 📄 Ultima Version               | 0.18.2             | 0.17.2  | 0.0.3              |
| 📆 Fecha Ultima Version          | 9 Nov 2023         | 30 sep 2023 | 30 jun 2020   |
| 📄 Tipo de licencia             | Apache-2.0 license | MIT     | MIT                |
| 📄 Languages  | Python 95.1%, Jupyter Notebook 4.3%, Jinja 0.4%, JavaScript 0.1%, CSS 0.1%, HTML 0.0% | Python 99.9%, Makefile 0.1% | Python100%



## Diferencias entre Licencia Apache 2.0 y MIT

-   **Notificación de Cambios:**
    -   **Apache 2.0:** Requiere que se notifiquen los cambios realizados en el código fuente cuando se distribuye el software.
    -   **MIT:** No requiere notificación específica de cambios.
-   **Compatibilidad:**
    -   **Apache 2.0:** Es conocida por ser compatible con más licencias en comparación con MIT.
    -   **MIT:** También es bastante compatible con diversas licencias, pero la Licencia Apache 2.0 a menudo se elige en proyectos que buscan una mayor interoperabilidad con otras licencias.
-   **Atribución:**
    -   **Apache 2.0:** Requiere atribución y la inclusión de un aviso de derechos de autor.
    -   **MIT:** Requiere atribución de la autoría original, pero puede tener requisitos menos estrictos en términos de cómo se muestra esa atribución.

> Teniendo en cuenta estas metricas actualmente analizadas vamos a realizar un ejemplo de una implementación utilizando Pandera y Great expectations

--------------------------------------------------------------

## Dataset

Para el desarrollo de este ejemplo utilizaremos el conjunto de datos denominado `Tips`, el conjunto de datos se puede descargar del siguiente [link](https://raw.githubusercontent.com/mwaskom/seaborn-data/master/tips.csv).

El conjunto de datos "tips" que contiene información sobre las propinas dadas en un restaurante, junto con detalles sobre la factura total, el sexo de la persona que pagó la factura, si el cliente es fumador, el día de la semana y la hora de la comida.


| **Columna**  | **Descripción**    |
| -----------  | -----------    |                                   
| total_bill   | El monto total de la factura (incluida la propina).   |
| tip          | La cantidad de propina dada.                          
| sex          | El género del pagador de la factura (hombre o mujer). |
| smoker       | Si el cliente es fumador o no.                        
| day          | El día de la semana en que se realizó la comida.      |
| time         | El momento del día (almuerzo o cena).                 
| size         | El tamaño del grupo que compartió la comida           



A continuación una tabla con las primeras 5 filas del conjunto de datos:

| total_bill | tip  | sex    | smoker | day | time   | size |
|------------|------|--------|--------|-----|--------|------|
| 16.99      | 1.01 | Female | No     | Sun | Dinner | 2    |
| 10.34      | 1.66 | Male   | No     | Sun | Dinner | 3    |
| 21.01      | 3.50 | Male   | No     | Sun | Dinner | 3    |
| 23.68      | 3.31 | Male   | No     | Sun | Dinner | 2    |
| 24.59      | 3.61 | Female | No     | Sun | Dinner | 4    |

--------------------------------------------------------------

## 🟢 Pandera

A continución vamos a realizar un ejemplo de implementación de Pandera utilizando el conjunto de datos anteriormente descripto.

### Instalar pandera

``` {.bash}
pip install pandas pandera 
```

### Ejemplo de implementación

1️⃣ Importar pandas y pandera

``` {.python}
import pandas as pd
import pandera as pa
```

2️⃣ Importar el archivo del dataframe

``` {.python}
path = 'data/tips.csv'
data = pd.read_csv(path)

print(f"Numero de columnas: {data.shape[1]}, Numero de filas: {data.shape[0]}")
print(f"Nombre de columnas: {list(data.columns)}")
```

``` {.python}
data.info()
```

``` {.bash}
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 244 entries, 0 to 243
Data columns (total 7 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   total_bill  244 non-null    float64
 1   tip         244 non-null    float64
 2   sex         244 non-null    object 
 3   smoker      244 non-null    object 
 4   day         244 non-null    object 
 5   time        244 non-null    object 
 6   size        244 non-null    int64  
dtypes: float64(2), int64(1), object(4)
memory usage: 13.5+ KB
```

3️⃣ Ahora vamos a crear el objeto **schema** que tiene todas las validaciones que queremos realizar.

En el siguiente link puede encontrar otras validaciones que se pueden realizar https://pandera.readthedocs.io/en/stable/dtype_validation.html

``` {.python}
schema = pa.DataFrameSchema({
  "total_bill": pa.Column(float, checks=pa.Check.le(50)),
  "tip"       : pa.Column(float, checks=pa.Check.between(0,30)),
  "sex"       : pa.Column(str, checks=[pa.Check.isin(['Female','Male'])]),
  "smoker"    : pa.Column(str, checks=[pa.Check.isin(['No','Yes'])]),
  "day"       : pa.Column(str, checks=[pa.Check.isin(['Sun','Sat'])]),
  "time"      : pa.Column(str, checks=[pa.Check.isin(['Dinner','Lunch'])]),
  "size"      : pa.Column(int, checks=[pa.Check.between(1,4)])
})
```

4️⃣ Para poder obtener el error y posteriormente poder utilizar este output para analizarlo es necesario capturar el mismo con un exception.

``` {.python}
try:
    schema(data).validate()
except Exception as e:
    print(e)
    error = e
```

``` {.bash}
Schema None: A total of 3 schema errors were found.

Error Counts
------------
- SchemaErrorReason.SCHEMA_COMPONENT_CHECK: 3

Schema Error Summary
--------------------
schema_context column     check                     failure_cases  n_failure_cases
                                                   
Column         day        isin(['Sun', 'Sat'])      [Thur, Fri]             2
               size       in_range(1, 4)              [5, 6]                2
               total_bill less_than_or_equal_to(50)   [50.81]               1
```

5️⃣ A continuación tienes una función que te permite transformar el output en un diccionario o en un dataframe de pandas.

``` {.python}
def get_errors(error, dtype_dict=True):
    response = []

 
    for item in range(len(error.schema_errors)):
        error_item = error.schema_errors[item]
        response.append(
        {
            'column'     :error_item.schema.name,
            'check_error':error_item.schema.checks[0].error,
            'num_cases'  :error_item.failure_cases.index.shape[0],
            'check_rows' :error_item.failure_cases.to_dict()
        })
    
    if dtype_dict:
        return response
    else:
        return pd.DataFrame(response)
```

``` {.python}
get_errors(error,dtype_dict=True)
```

``` {.bash}
[{'column': 'total_bill',
  'check_error': 'less_than_or_equal_to(50)',
  'num_cases': 1,
  'check_rows': {'index': {0: 170}, 'failure_case': {0: 50.81}}},
 {'column': 'day',
  'check_error': "isin(['Sun', 'Sat'])",
  'num_cases': 81,
  'check_rows': {'index': {0: 77,
    1: 78,
    2: 79,
    3: 80,
    4: 81,
    5: 82,
    6: 83,
    7: 84,
...
    5: 156,
    6: 185,
    7: 187,
    8: 216},
   'failure_case': {0: 6, 1: 6, 2: 5, 3: 6, 4: 5, 5: 6, 6: 5, 7: 5, 8: 5}}}]
```

--------------------------------------------------------------

## 🟠 Great Expectations

`Great Expectations` es una biblioteca de código abierto basada en Python para validar, documentar y crear perfiles de sus datos.
La cual ayuda a mantener la calidad de los datos y mejorar la comunicación sobre los datos entre equipos.

![](https://i.ibb.co/xHMwVmW/gx.png)

source : <https://docs.greatexpectations.io/docs/>

Por lo cual podemos describir a Great Expectations como una herramienta de código abierto diseñada para garantizar la calidad y confiabilidad de los datos en diversas fuentes, como bases de datos, tablas, archivos y dataframes.
Su funcionamiento se basa en la creación de grupos de validaciones que especifican las expectativas o reglas que los datos deben cumplir.

Los siguientes son los pasos que debemos definir cuando utilizamos este framework:

1️⃣ **Definición de Expectativas**: Se especifican las expectativas que tienen sobre los datos.
Estas expectativas pueden incluir restricciones simples, como rangos de valores, o reglas más complejas sobre la coherencia y calidad de los datos.

2️⃣ **Conexión a Fuentes de Datos**: En este paso se debe definir cuales son las conexiones que debemos realizar a las diversas fuentes de datos, como bases de datos, tablas, archivos o dataframes.

3️⃣ **Generación de Suites de Validaciones**: A partir de las expectativas definidas, Great Expectations genera suites de validaciones que son conjuntos organizados de reglas que se aplicarán a los datos.

4️⃣ **Ejecución de Validaciones**: Las suites de validaciones se aplican a los datos, verificando si cumplen con las expectativas definidas.
Esto puede realizarse automáticamente en un flujo de trabajo programado o de forma interactiva según sea necesario.

5️⃣ **Generación de Análisis y Reportes**: Great Expectations proporciona capacidades avanzadas de análisis y generación de informes.
Esto incluye perfiles detallados de la calidad de los datos y reportes que resumen la salud general de los datos en función de las expectativas.

6️⃣ **Alertas y Notificaciones**: En caso de que los datos no cumplan con las expectativas definidas, Great Expectations puede generar alertas o notificaciones, permitiendo a los usuarios tomar medidas inmediatas para abordar problemas de calidad de datos.

En conjunto, Great Expectations ofrece una solución integral para garantizar la calidad de los datos a lo largo del tiempo, facilitando la detección temprana de problemas y brindando confianza en la integridad y utilidad de los datos utilizados en análisis y toma de decisiones.

### Instalación de great expectation

``` {.shell}
!pip install great_expectations==0.17.22 seaborn matplotlib numpy pandas
```

### Ejemplo de implementación

``` {.python}
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import os
import re 

import great_expectations as gx
from ruamel.yaml import YAML
from great_expectations.cli.datasource import sanitize_yaml_and_save_datasource
from great_expectations.core.expectation_configuration import ExpectationConfiguration

print(f"* great expectations version:{gx.__version__}")
print(f"* seaborn version:{sns.__version__}")
print(f"* numpy version:{np.__version__}")
print(f"* pandas:{pd.__version__}")
```

``` {.shell}
* great expectations version:0.17.22
* seaborn version:0.13.0
* numpy version:1.26.1
* pandas:2.1.3
```

1️⃣ Importar dataset utilizando great expectation

``` {.python}
path = 'data/tips.csv'
data_gx = gx.read_csv(path)
```

2️⃣ Listar todas las expectation disponibles por tipo

``` {.python}
list_expectations = pd.DataFrame([item for item in dir(data_gx) if item.find('expect_')==0],columns=['expectation'])
list_expectations['expectation_type'] = np.select( [
        list_expectations.expectation.str.find('_table_')>0, 
        list_expectations.expectation.str.find('_column_')>0,  
        list_expectations.expectation.str.find('_multicolumn_')>0,
    ],['table','column','multicolumn'],
    default='other'
)

plt.figure(figsize=(20,6))
sns.countplot(x=list_expectations.expectation_type)
plt.show()
```

![](https://i.ibb.co/M8D2pyf/gx-output.png)

> En la imagen se observa que las expectations disponibles son principalmente aplicadas a columnas (como por ejemplo: **expect_column_max_to_be_between** ) y a tablas (como por ejemplo: **expect_table_columns_to_match_set**) , aunque tambien se pueden aplicar una expectation basada en los valores de varias columnas (como por ejemplo:**expect_multicolumn_values_to_be_unique**).

#### Expectations: Tables

``` {.python}
# La siguiente lista contiene las colmnas que debe tener el dataframe
columns = ['total_bill', 'tip', 'sex', 'smoker', 'day', 'time', 'size']
data_gx.expect_table_columns_to_match_set(column_set = columns)
```

``` {.bash}
{
  "success": true,
  "result": {
    "observed_value": [
      "total_bill",
      "tip",
      "sex",
      "smoker",
      "day",
      "time",
      "size"
    ]
  },
  "meta": {},
  "exception_info": {
    "raised_exception": false,
    "exception_traceback": null,
    "exception_message": null
  }
}
```

``` {.python}
# Ahora borramos dos columnas que son "time" y "size" para validar cual es el resultado.

columns = = ['total_bill', 'tip', 'sex', 'smoker', 'day']
data_gx.expect_table_columns_to_match_set(column_set = columns)
```

> Si observamos el resultado es `False` y en details nos detallan cuales son las columnas que el dataframe tiene adicionales a las que se esperaban.

``` {.bash}
{
  "success": false,
  "result": {
    "observed_value": [
      "day",
      "sex",
      "size",
      "smoker",
      "time",
      "tip",
      "total_bill"
    ],
    "details": {
      "mismatched": {
        "unexpected": [
          "size",
          "time"
        ]
      }
    }
  },
  "meta": {},
  "exception_info": {
    "raised_exception": false,
    "exception_traceback": null,
    "exception_message": null
  }
}
```

#### Expectations: Columns

1️⃣ Validemos que existe un valor categorico dentro de una columna

``` {.python}
data_gx['total_bill_group'] = pd.cut(data_gx['total_bill'],
                              bins=[0,10,20,30,40,50,float('inf')], 
                              labels=['0-10', '10-20', '20-30', '30-40', '40-50', '>50'],
                              right=False, 
                              include_lowest=True)

# Ahora validamos si 3 categorias existen dentro del conjunto de datos

data_gx.expect_column_distinct_values_to_contain_set(column='total_bill_group',
                                                      value_set=['0-10','10-20', '20-30'],
                                                      result_format='BASIC') 
```

``` {.bash}
{
  "success": true,
  "result": {
    "observed_value": [
      "0-10",
      "10-20",
      "20-30",
      "30-40",
      "40-50",
      ">50"
    ],
    "element_count": 244,
    "missing_count": null,
    "missing_percent": null
  },
  "meta": {},
  "exception_info": {
    "raised_exception": false,
    "exception_traceback": null,
    "exception_message": null
  }
}
```

2️⃣ Validemos que la columna no tiene valor nulos

``` {.python}
data_gx.expect_column_values_to_not_be_null('sex')
```

``` {.bash}
{
  "success": true,
  "result": {
    "element_count": 244,
    "unexpected_count": 0,
    "unexpected_percent": 0.0,
    "unexpected_percent_total": 0.0,
    "partial_unexpected_list": []
  },
  "meta": {},
  "exception_info": {
    "raised_exception": false,
    "exception_traceback": null,
    "exception_message": null
  }
}
```

#### Great Expectation Project

Ahora vamos a genera un proyecto de great expectation para poder ejecutar un grupo de validaciones basados en uno o varios conjuntos de datos.

1️⃣ Inicializar el proyecto de great expectation

``` {.python}
 !yes Y | great_expectations init
```

``` {.bash}
  ___              _     ___                  _        _   _
 / __|_ _ ___ __ _| |_  | __|_ ___ __  ___ __| |_ __ _| |_(_)___ _ _  ___
| (_ | '_/ -_) _` |  _| | _|\ \ / '_ \/ -_) _|  _/ _` |  _| / _ \ ' \(_-<
 \___|_| \___\__,_|\__| |___/_\_\ .__/\___\__|\__\__,_|\__|_\___/_||_/__/
                                |_|
             ~ Always know what to expect from your data ~

Let's create a new Data Context to hold your project configuration.

Great Expectations will create a new directory with the following structure:

    great_expectations
    |-- great_expectations.yml
    |-- expectations
    |-- checkpoints
    |-- plugins
    |-- .gitignore
    |-- uncommitted
        |-- config_variables.yml
        |-- data_docs
        |-- validations

OK to proceed? [Y/n]: 
================================================================================

Congratulations! You are now ready to customize your Great Expectations configuration.

You can customize your configuration in many ways. Here are some examples:

  Use the CLI to:
    - Run `great_expectations datasource new` to connect to your data.
    - Run `great_expectations checkpoint new <checkpoint_name>` to bundle data with Expectation Suite(s) in a Checkpoint for later re-validation.
    - Run `great_expectations suite --help` to create, edit, list, profile Expectation Suites.
    - Run `great_expectations docs --help` to build and manage Data Docs sites.

  Edit your configuration in great_expectations.yml to:
    - Move Stores to the cloud
    - Add Slack notifications, PagerDuty alerts, etc.
    - Customize your Data Docs

Please see our documentation for more configuration options!
```

2️⃣ Copiar datos dentro de la carpeta de "gx" generada a partir de la inicialización del proyecto

``` {.python}
!cp -r data gx
```

``` {.python}
# vamos a imprimir el contenido de la carpeta

def print_directory_structure(directory_path, indent=0):
    current_dir = os.path.basename(directory_path)
    print("    |" + "    " * indent + f"-- {current_dir}")
    indent += 1
    with os.scandir(directory_path) as entries:
        for entry in entries:
            if entry.is_dir():
                print_directory_structure(entry.path, indent)
            else:
                print("    |" + "    " * indent + f"-- {entry.name}")


print_directory_structure('gx')
```

``` {.bash}
    |-- gx
    |    -- great_expectations.yml
    |    -- plugins
    |        -- custom_data_docs
    |            -- renderers
    |            -- styles
    |                -- data_docs_custom_styles.css
    |            -- views
    |    -- checkpoints
    |    -- expectations
    |        -- .ge_store_backend_id
    |    -- profilers
    |    -- .gitignore
    |    -- data
    |        -- tips.csv
    |    -- uncommitted
    |        -- data_docs
    |        -- config_variables.yml
    |        -- validations
    |            -- .ge_store_backend_id
```

Algunas aclaraciones sobre los archivos y carpetas que se generan en esta carpeta:


| Archivos/Carpetas           | Descripción                                                                                                                                                                                              |
|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 📄great_expectations.yml  | Este archivo contiene la configuración principal del proyecto. Aquí se especifican detalles como las ubicaciones de almacenamiento y otros parámetros de configuración.                                 |
| 📂 plugins                  | - 📄 custom_data_docs: Contiene renderizadores personalizados para documentos de datos. <br> - 📄 renderers: Incluye estilos personalizados para los documentos de datos, como hojas de estilo CSS (data_docs_custom_styles.css). <br> - 📄 views: Puede contener vistas personalizadas para los documentos de datos. |
| 📂 checkpoints              | Esta carpeta podría contener definiciones de checkpoints, que son puntos en el flujo de datos donde se pueden realizar validaciones específicas. |                                                          
| 📂 expectations             | Aquí se almacenan las expectativas definidas para los datos. Este directorio puede contener varias subcarpetas y archivos, dependiendo de la organización del proyecto.                                   |
| 📂 profilers                | Puede contener configuraciones para perfiles de datos, que son análisis detallados de las estadísticas de los datos.   |                                                                                  
| 📄 .gitignore               | Es un archivo de configuración para Git que especifica archivos y carpetas que deben ignorarse al realizar operaciones de seguimiento y compromiso (commit).                                            |
| 📂 data                     | Contiene los datos utilizados en el proyecto, en este caso, el archivo tips.csv. |                                                                                                                         
| 📂 uncommitted              | - 📂 data_docs: Carpeta donde se generan documentos de datos. <br> - 📄 config_variables.yml: Archivo de configuración que puede contener variables específicas del proyecto. <br> - 📂 validations: Puede contener resultados de validaciones realizadas en los datos. | 

3️⃣ Configuración de datasource y data conectors

> **DataSource:** Es el origen de datos utilizado (puede ser un archivo, API, base de datos, entre otros.
>
> **Data Connectors**: Son los conectores que facilitan la conexion a la fuentes de datos y donde se deben definir las credenciales de acceso, ubicación, entre otros.

``` {.python}
datasource_name_file = 'tips.csv'
datasource_name = 'datasource_tips'
dataconnector_name = 'connector_tips'
```

``` {.python}
# Creamos la configuración del datasource

context = gx.data_context.DataContext()
my_datasource_config = f"""
    name: {datasource_name}
    class_name: Datasource
    execution_engine:
      class_name: PandasExecutionEngine
    data_connectors:
      {dataconnector_name}:
        class_name: InferredAssetFilesystemDataConnector
        base_directory: data
        default_regex:
          group_names:
            - data_asset_name
          pattern: (.*)
      default_runtime_data_connector_name:
        class_name: RuntimeDataConnector
        assets:
          my_runtime_asset_name:
            batch_identifiers:
              - runtime_batch_identifier_name
"""

yaml = YAML()
context.add_datasource(**yaml.load(my_datasource_config))
sanitize_yaml_and_save_datasource(context, my_datasource_config, overwrite_existing=True)
```

4️⃣ Configuración de las expectations

> En el siguiente fragmento de código, se presenta la configuración de tres expectativas (expectations).
>
> En particular, la última de ellas incluye un parámetro denominado `"mostly"` con un valor de 0.75.
> Este parámetro indica que la expectativa puede fallar en hasta un 25% de los casos, ya que, por defecto, se espera un cumplimiento del 100% a menos que se especifique lo contrario.
>
> Adicionalmente se puede especificar un mensaje de error en formato markdown, tal como se visualiza en la ultima expectation.

``` {.python}
expectation_configuration_table =  ExpectationConfiguration(
   expectation_type="expect_table_columns_to_match_set",
      kwargs= {
        "column_set": ['total_bill', 'tip', 'sex', 'smoker', 'day', 'time', 'size']
      },
      meta= {}
)

expectation_configuration_total_bill = ExpectationConfiguration(
      expectation_type= "expect_column_values_to_be_between",
      kwargs= {
        "column": "total_bill",
        "min_value": 0,
        "max_value": 100
      },
      meta= {}
)


expectation_configuration_size = ExpectationConfiguration(
   expectation_type="expect_column_values_to_not_be_null",
   kwargs={
      "column": "size",
      "mostly": 0.75,
   },
   meta={
      "notes": {
         "format": "markdown",
         "content": "Expectation to validate column `size` does not have null values."
      }
   }
)
```

5️⃣ Creación de la suite expectation

``` {.python}
expectation_suite_name = "tips_expectation_suite"
expectation_suite = context.create_expectation_suite(
    expectation_suite_name=expectation_suite_name, 
    overwrite_existing=True
)

# Add expectations
expectation_suite.add_expectation(expectation_configuration=expectation_configuration_table)
expectation_suite.add_expectation(expectation_configuration=expectation_configuration_total_bill)
expectation_suite.add_expectation(expectation_configuration=expectation_configuration_size)

# save expectation_suite
context.save_expectation_suite(expectation_suite=expectation_suite, 
                               expectation_suite_name=expectation_suite_name)
```

``` {.bash}
data-quality/gx/expectations/tips_expectation_suite.json
```

> Dentro de la carpeta `expectations` se crea un archivo json con todas las expectations generadas anteriormente.

6️⃣ Configuración de los checkpoints

``` {.python}
checkpoint_name ='tips_checkpoint'

config_checkpoint = f"""
    name: {checkpoint_name}
    config_version: 1
    class_name: SimpleCheckpoint
    expectation_suite_name: {expectation_suite_name}
    validations:
      - batch_request:
          datasource_name: {datasource_name}
          data_connector_name: {dataconnector_name}
          data_asset_name: {datasource_name_file}
          batch_spec_passthrough:
            reader_method: read_csv
            reader_options: 
              sep: ","
          data_connector_query:
            index: -1
        expectation_suite_name: {expectation_suite_name}
"""

# Validar si la estructura del yaml es correcta
context.test_yaml_config(config_checkpoint)

# Agregar el checkpoint al contexto generado
context.add_checkpoint(**yaml.load(config_checkpoint)) 
```

7️⃣ Ejecutar el checkpoint para validar todas las expectations configuradas sobre el conjunto de datos

``` {.python}
response = context.run_checkpoint(checkpoint_name=checkpoint_name)
```

8️⃣ Para observar el resultado obtenido de las validaciones se puede convertir a json

``` {.python}
 response.to_json_dict()
```

``` {.bash}
{'run_id': {'run_name': None, 'run_time': '2023-11-12T20:39:23.346946+01:00'},
 'run_results': {'ValidationResultIdentifier::tips_expectation_suite/__none__/20231112T193923.346946Z/722b2e93e32fd7222c8ad9339f3e0e1d': {'validation_result': {'success': True,
    'results': [{'success': True,
      'expectation_config': {'expectation_type': 'expect_table_columns_to_match_set',
       'kwargs': {'column_set': ['total_bill',
         'tip',
         'sex',
         'smoker',
         'day',
         'time',
         'size'],
        'batch_id': '722b2e93e32fd7222c8ad9339f3e0e1d'},
       'meta': {}},
      'result': {'observed_value': ['total_bill',
        'tip',
        'sex',
        'smoker',
        'day',
        'time',
        'size']},
      'meta': {},
      'exception_info': {'raised_exception': False,
       'exception_traceback': None,
       'exception_message': None}},
     {'success': True,
...
  'notify_on': None,
  'default_validation_id': None,
  'site_names': None,
  'profilers': []},
 'success': True}
```

#### Obtener los resultados

``` {.python}
 context.open_data_docs()
```

> Al ejecutar este chunck de codigo se va abrir un archivo html con el resultado de las validaciones que esta en `gx/uncommitted/data_docs/local_site/validations/tips_expectation_suite/__none__/20231112T192529.002401Z/722b2e93e32fd7222c8ad9339f3e0e1d.html`

![](https://i.ibb.co/YfzkPPb/gx-1.png)

![](https://i.ibb.co/SrmjBcM/gx-2.png)

![](https://i.ibb.co/Bs0LWjF/gx-3.png)

## Si quieres aprender...

1.  [Pandera Documentación Oficial](https://pandera.readthedocs.io)
2.  [Pandera: Statistical Data Validation of Pandas Dataframes - Researchgate](https://www.researchgate.net/publication/343231859_pandera_Statistical_Data_Validation_of_Pandas_Dataframes)
3.  [Great Expectation Documentación Oficial](https://docs.greatexpectations.io/docs/)
4.  [Data Quality Fundamentals Book O'relly](https://www.oreilly.com/library/view/data-quality-fundamentals/9781098112035/)
5.  [Great Expectation Yoututbe Channel](https://www.youtube.com/@GreatExpectationsData)