data-cleaner

Coverage Status Build Status PyPI Stories in Ready Documentation Status

Paquete para limpieza de datos, según los estándares de limpieza de la SSIPyGA - Gobierno Abierto Argentina

Nota: Este paquete aún se encuentra en etapa temprana de desarrollo y la interface podría sufrir modificaciones significativas.

  • Para referencia detallada de este paquete leer la [documentación] (http://data-cleaner.readthedocs.org/en/latest/) *
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*

Instalación

Requiere tener Python 2.7 instalado en el sistema. Si se va a instalar python desde cero, se recomienda instalar la distribución de Anaconda con Python 2.7, ya que viene con varias librerías preinstaladas.

Para uso simple:

pip install data_cleaner

Para desarrollo:

cd package_directory
pip install -e .

Uso

Lista de reglas

Se puede limpiar un CSV a través de una lista de reglas. El siguiente ejemplo toma un csv, capitaliza todos los strings en la columna “dependencia” y convierte todas las fechas de la columna “fecha_completa_audiencia” que sigan el formato 19-02-2016 09:11 al estándar ISO 8601 2016-02-19T09:11:00-03:00

from data_cleaner import DataCleaner

input_path = "samples/example.csv"
output_path = "samples/clean_example.csv"

rules = [
    {
        "nombre_propio": [
            {"field": "dependencia"}
        ]
    },
    {
        "fecha_completa": [
            {"field": "fecha_completa_audiencia",
             "time_format": "DD-MM-YYYY HH:mm"}
        ]
    }
]

dc = DataCleaner(input_path)
dc.clean_file(rules, output_path)

También se pueden limpiar los datos sin guardar el csv, para analizarlos en memoria.

dc.clean(rules)
dc.df  # accede al DataFrame donde están los datos

Métodos de limpieza

Las reglas de limpieza del cleaner también se pueden utilizar como métodos individuales que devuelven una pandas.DataSeries o un pandas.DataFrame (en el caso en que el método genere múltiples columnas nuevas).

dependencia_clean = dc.nombre_propio("dependencia")

print dependencia_clean

0    Presidencia De La Nación
1    Presidencia De La Nación
2    Presidencia De La Nación
3    Presidencia De La Nación
4    Presidencia De La Nación
Name: dependencia, dtype: object

Método de limpieza con parámetros.

fecha_audiencia_clean = dc.fecha_completa("fecha_audiencia",
                                          "DD-MM-YYYY HH:mm")

print fecha_audiencia_clean

0    2013-11-12T10:00:00-03:00
1    2014-12-13T10:50:00-03:00
2                          NaN
3                          NaN
4                          NaN
Name: fecha_audiencia, dtype: object

Si se desea que la limpieza practicada perdure en el objeto, se debe especificar el keyword argument inplace=True.

dc.nombre_propio("dependencia", inplace=True)

print dc.df.dependencia

0    Presidencia De La Nación
1    Presidencia De La Nación
2    Presidencia De La Nación
3    Presidencia De La Nación
4    Presidencia De La Nación
Name: dependencia, dtype: object

En todo momento se puede acceder al pandas.DataFrame que contiene la tabla de datos, donde se verán reflejados los cambios luego de aplicar métodos de limpieza con el parámetro inplace=True.

dc.df  # accede al pandas.DataFrame del cleaner

Para guardar el pandas.DataFrame en cualquier momento, probablemente luego de probar y aplicar algunas transformaciones.

dc.save(output_path)

El método DataCleaner.save() redirige al método pandas.DataFrame.to_csv(), y por lo tanto tienen los mismos argumentos.

Encoding del input, y otros

Se asume que el input es un csv encodeado en utf-8, separado por comas y que usa comillas dobles para el enclosing. Si alguno de estos parámetros (especialmente el enconding) es diferente, debe especificarse.

dc = DataCleaner("ugly.csv", encoding="latin1", sep=";", quotechar="'")

Limpieza automática

Formato del archivo limpio

Luego de la limpieza los datos se guardan siempre en un archivo CSV, encodeado en utf-8 separado por ”,” y usando ‘”’ como caracter de citas.

Nombres de los campos

Los nombres de los campos se normalizan automáticamente. Sólo el uso de caracteres alfanuméricos ASCII y “_” está permitido. Los campos deben nombrarse con palabras en minúsculas separadas por guión bajo. Para esto el objeto:

  • Reemplaza espacios y “-” por “_“
  • Reemplaza todos los caracteres alfanuméricos por su versión ASCII más próxima
  • Remueve todos los caracteres especiales que no sean “_“

Saltos de línea

No se permiten saltos de línea en los valores, al momento de crear un objeto DataCleaner se reemplazan todos los saltos de línea que estén dentro del caracter de enclosing (usualmente comillas dobles ‘”’) por un espacio ” ”.

Template de script de limpieza

Para realizar la limpieza de un archivo CSV de datos con data-cleaner se sugiere utilizar el template de script de limpieza. Este permite correr la limpieza desde la línea de comandos e implementar pasos de limpieza personalizados que exceden las funcionalidades del paquete.

Reglas de limpieza

Son diccionarios cuyas keys son los nombres de las reglas de limpieza y cuyos values son (a) lista de columnas donde aplicar la regla -en el caso en que la regla no requiera otros parámetros- o (b) lista de parámetros que necesita la regla para funcionar -donde el primer parámetro es siempre el campo donde aplicar la regla-.

Renombrar columnas (renombrar_columnas)

Renombra columnas de la tabla de datos.

Especificación:

{"renombrar_columnas": [
    {"field": "columna_actual_1", "new_field": "columna_nueva_1"},
    {"field": "columna_actual_2", "new_field": "columna_nueva_2"},
    {"field": "columna_actual_3", "new_field": "columna_nueva_3"}
]}

Ejemplo:

{"renombrar_columnas": [
    {"field": "aut_dependencia", "new_field": "dependencia"},
    {"field": "sujeto_obligado_audiencia", "new_field": "sujeto_obligado"}
]}

Remover columnas (remover_columnas)

Remueve campos de la tabla de datos.

Entre otras cosas, se puede utilizar para remover los campos originales -no recomendado- que dieron origen a múltiples campos nuevos cuando se utilizó alguna regla de split.

Especificación:

{"remover_columnas": [
    {"field": "columna_a_remover_1"},
    {"field": "columna_a_remover_2"}
]}

Ejemplo:

{"remover_columnas": [
    {"field": "dependencia"},
    {"field": "fecha_completa_audiencia"}
]}

Capitalizar nombres propios (nombre_propio)

Normaliza todas las palabras que encuentra poniéndolas en minúsculas y capitalizando la primera letra de cada una.

Se aplica a todos aquellos campos de datos que tengan nombres de personas. En el caso de direcciones, ciudades, países, organismos e instituciones debe aplicarse con mucha cautela, existen casos donde esta regla de limpieza hace más mal que bien (ej.: las instituciones pueden tener siglas, que no corresponde capitalizar).

Argumentos opcionales:

  • keep_original: True para conservar la columna original / False para removerla (Default: False)
  • sufix: Sufijo para agregar a la nueva columna limpia (Default: “clean”)

Especificación:

{"nombre_propio": [
    {"field": "columna_1"},
    {"field": "columna_2"}
]}

Ejemplo:

{"nombre_propio": [
    {"field": "dependencia"}
]}

Dar formato a correo electronico (mail_format)

Parsea todas las direcciones de correo electrónico en cada fila de una campo y les da el formato estandar definido. Es decir, las pasa todas a minúsculas y las separa con comas.

Argumentos opcionales:

  • keep_original: True para conservar la columna original / False para removerla (Default: False)
  • sufix: Sufijo para agregar a la nueva columna limpia (Default: “clean”)

Especificación:

{"mail_format": [
    {"field": "columna_1"},
    {"field": "columna_2"}
]}

Ejemplo:

{"mail_format": [
    {"field": "correo_electronico"}
]}

Normalizar strings (string)

Utiliza el algoritmo Key Collision Fingerprint para clusterizar strings con el mismo contenido, normalizando capitalización, acentos, caracteres especiales, etc.

Este algoritmo busca unificar la forma de escribir strings que contienen idénticas palabras (cadenas de caracteres alfanuméricos separados por espacios) pero difieren en otros aspectos. Para más detalle ver Key Collision Methods de OpenRefine. La implementación que se utiliza es una adaptación de esta, publicada en Github por Tyler Weirick.

Argumentos opcionales:

  • sort_tokens: False (default) para no ordenar las palabras al crear el fingerprint de un string. Esto ubicará a “Sol Geriatrico” y “Geriatrico Sol” en clusters separados, sin unificar el string en un sentido o en otro. Si se especifica True, ambos strings se reescribirían de una de las dos maneras.
  • remove_duplicates: False (default) para evitar remover tokens duplicados. Esto ubicará a “Sol Sol Geriatrico” en un cluster distinto a “Sol Geriatrico”, sin elegir una forma de escribir el string para ambos casos. Si se especifica True, ambos strings se escribirían de una de las dos maneras.
  • keep_original: True para conservar la columna original / False para removerla (Default: False)
  • sufix: Sufijo para agregar a la nueva columna limpia (Default: “clean”)

Especificación:

{"string": [
    {"field": "columna_1"},
    {"field": "columna_2"}
]}

Ejemplo:

{"string": [
    {"field": "dependencia"},
    {"field": "lugar_audiencia"},
    {"field": "sujeto_obligado"},
    {"field": "solicitante"}
]}

Reemplazar listas de strings por valores predefinidos (reemplazar)

Reemplaza listas de strings por un valor predefinido que el usuario decide que representa a todas. Solo sirve para reemplazar valores completos

Argumentos opcionales:

  • keep_original: True para conservar la columna original / False para removerla (Default: False)
  • sufix: Sufijo para agregar a la nueva columna limpia (Default: “clean”)

Especificación:

{"reemplazar": [
    {
     "field": "columna",
     "replacements": {"Nuevo1": ["Viejo"], "Nuevo2": ["ViejoA", "ViejoB"]}
    }
]}

Ejemplo:

{"reemplazar": [
    {
    "field": "tipo",
    "replacements": {"Servicios": ["Serv"], "Otros": ["Otro", "Loc"]}
    }
]}

En este ejemplo si el campo tipo tuviese el valor “Serv de venta” no sería reemplazado, mientras que si tuviese el valor “Serv” sería reemplazado por “Servicios”

Reemplazar partes de valores (substrings) por otros (reemplazar_string)

Reemplaza listas de substrings por otro substring. A diferencia del método reemplazar que reemplaza directamente valores completos, reemplazar_string hace reemplazos parciales. Es una versión más sencilla de string_regex_substitute que no permite evaluar expresiones regulares.

Argumentos opcionales:

  • keep_original: True para conservar la columna original / False para removerla (Default: False)
  • sufix: Sufijo para agregar a la nueva columna limpia (Default: “clean”)

Especificación:

{"reemplazar_string": [
    {
     "field": "columna",
     "replacements": {"Nuevo1": ["Viejo"], "Nuevo2": ["ViejoA", "ViejoB"]}
    }
]}

Ejemplo:

{"reemplazar_string": [
    {
    "field": "tipo",
    "replacements": {"Servicios": ["Serv"], "Otros": ["Otro", "Loc"]}
    }
]}

En este ejemplo si el campo tipo tuviese el valor “Serv de venta” sería reemplazado por “Servicios de Venta”.

Normalizar fecha completa (fecha_completa)

Estandariza un campo con fecha y hora a su representación en el estándar ISO 8601 (YYYY-MM-DDTHH:MM:SS[.mmmmmm][+HH:MM]).

Ej.: 05-02-2016 14:53 a 2016-02-05T14:53:00-03:00

Para el parsing de fechas se utiliza la librería arrow. En la regla debe especificarse el formato temporal en que la fecha está expresada en la tabla de datos original. El resultado siempre se convertirá a ISO 8601 cuando sea posible, ante cualquier error se dejará la celda vacía.

Argumentos opcionales:

  • keep_original: True para conservar la columna original / False para removerla (Default: False)

Especificación:

{"fecha_completa": [
    {"field": "columna", "time_format": "DD-MM-YYYY HH:mm"}
]}

Ejemplo:

{"fecha_completa": [
    {"field": "fecha_completa_audiencia", "time_format": "DD-MM-YYYY HH:mm"}
]}

Normalizar fecha simple (fecha_simple)

Estandariza un campo sin hora, día o mes a su representación en el estándar ISO 8601, obviando aquella parte de la representación ISO para la que no se cuenta con datos suficientes.

Ej.: 05-02-2016 a 2016-02-05 Ej.: 02-2016 a 2016-02

Argumentos opcionales:

  • keep_original: True para conservar la columna original / False para removerla (Default: False)

Especificación:

{"fecha_simple": [
    {"field": "columna1", "time_format": "DD-MM-YYYY"},
    {"field": "columna2", "time_format": "MM-YYYY"}
]}

Ejemplo:

{"fecha_simple": [
    {"field": "fecha", "time_format": "DD-MM-YYYY"},
    {"field": "mes", "time_format": "MM-YYYY"}
]}

Normalizar fecha separada en múltiples campos (fecha_separada)

Estandariza una fecha completa donde distintos componentes de la misma están separados en varios campos, a su representación en el estándar ISO 8601.

Argumentos opcionales:

  • keep_original: True para conservar la columna original / False para removerla (Default: False)

Especificación:

{"fecha_separada": [
    {"fields": [["campo1", "DD-MM-YYYY"], ["campo2", "HH:mm"]],
     "new_field_name": "audiencia"}
]}

Ejemplo:

{"fecha_separada": [
    {"fields": [["fecha_audiencia", "DD-MM-YYYY"], ["hora_audiencia", "HH:mm"]], "new_field_name": "audiencia"}
]}

Separar campos mediante un separador simple (string_simple_split)

Separa strings de un campo en múltiples campos, mediante separadores simples.

Argumentos opcionales:

  • keep_original: True para conservar la columna original / False para removerla (Default: False)

Especificación:

{"string_simple_split": [
    {"field": "campo",
    "separators": ["separador_A", "separador_B"],
    "new_field_names": ["sufijo_nuevo_campo_1", "sufijo_nuevo_campo_2"]}
]}

Ejemplo:

{"string_simple_split": [
    {"field": "sujeto_obligado",
    "separators": [", Cargo:", "Cargo:"],
    "new_field_names": ["nombre", "cargo"]}
]}

Separar campos mediante una expresión regular (string_regex_split)

(NO IMPLEMENTADO)

Separar campos mediante una parsing expression grammar (string_peg_split)

Utiliza parsing expression grammars para separar strings de un campo en múltiples campos.

Las PEG son una forma de utilizar expresiones regulares de más alto nivel, que facilita la creación de reglas bastante complejas. La librería que se utiliza en este paquete es parsley.

Todas las PEG que se escriban para este paquete, deben contener una regla values cuyo output sea una lista de los valores que se quiere extraer. Cuando la PEG utilizada falle, el paquete dejará un valor nulo para esa celda.

Argumentos opcionales:

  • keep_original: True para conservar la columna original / False para removerla (Default: False)

Especificación:

{"string_peg_split": [
    {"field": "campo",
    "grammar": "grammar",
    "new_field_names": ["sufijo_nuevo_campo_1", "sufijo_nuevo_campo_2"]}
]}

Ejemplo:

{"string_peg_split": [
    {
    "field": "solicitante",
    "grammar": """
    allowed_char = anything:x ?(x not in '1234567890() ')
    nombre = ~('DNI') <allowed_char+>:n ws -> n.strip()
    number = <digit+>:num -> int(num)

    nom_comp = <nombre+>:nc -> nc.strip()
    cargo = '(' <nombre+>:c ')' -> c.strip()
    dni = ','? ws 'DNI' ws number:num -> num

    values = nom_comp:n ws cargo?:c ws dni?:d ws anything* -> [n, c, d]
    """,
    "new_field_names": ["nombre", "cargo", "dni"]
    }
]}

Manipular y reemplazar contenido de campos mediante una expression regular (string_regex_substitute)

Es análogo al método sub de la libreria de python re.

Argumentos opcionales:

  • keep_original: True para conservar la columna original / False para removerla (Default: False)
  • sufix: Sufijo para agregar a la nueva columna limpia (Default: “clean”)

Especificación:

{"string_regex_substitute":[
    {"field": "campo1",
    "regex_str_match": "str_regex_match1",
    "regex_str_sub": "str_regex_replace1"},
    {"field": "campo2",
    "regex_str_match": "str_regex_match2",
    "regex_str_sub": "str_regex_replace2"}
]}

Ejemplos:

Reemplaza punto y comas por comas:
{"string_regex_substitute":[
    {"field": "norma_competencias_objetivos",
    "regex_str_match": ";",
    "regex_str_sub": ","}
]}

Cambia el orden de una cadena entre parentesis:
{"string_regex_substitute":[
    {"field": "nombre_cargo",
    "regex_str_match": "(?P<cargo>\(.+\))(?P<nombre>.+)",
    "regex_str_sub": "\g<nombre> \g<cargo>"}
]}
"(presidente)Juan Jose Perez."  pasaría a ser "Juan Jose Perez. (presidente)"