<jupyter_start><jupyter_text>Métodos de Representación y Visualización Espacial HHDDTrabajo de evaluación curso 2021-22Profesor <NAME> DescripciónEl presente [Jupyter notebook](https://jupyter.org/) realiza el procesado de datos parte del trabajo de evaluación de la asignatura. El ejercicio consiste en acceder a [Soundtoll Database](http://www.soundtoll.nl/index.php/en/over-het-project/str-online), seleccionar una mercancía y periodo de tiempo y descargar en formato CSV la información relativa a _cargoes_ y _passages_. Parámetros de entradaLos parámetros de búsqueda en la Soundtoll Database son:* mercancía: **ámbar**, la cadena de búsqueda usada ha sido `B*rnste*n`, la cual cubre _Barnsteen_ (danés), _Bernstein_ (neerlandés) y diversas variaciones.* periodo: **1634-1857** Los archivo CSV resultantes están almacenados en el directorio `data/`. FuncionalidadesEl código realiza las siguientes operaciones sobre los archivos CSV:* genera un campo _fecha_ combinando los campos _día_, _month_ and _year_ del dataset _passages_* filtra los registros del dataset _cargoes_ asociados con ámbar en cualquiera de sus traducciones soportadas (DK, NL)* elimina los registros asociados a puertos excluidos del estudio* hace uniformes los nombres de puertos basado en un conjunto de reglas preestablecidas, este paso elimina la variabilidad presente en los puertos* selecciona unidades de peso soportadas (e.g. skippund, pund, ..) y las convierte a Kg* asocia las coordenadas de longitud y latitud a cada uno de los puertos* combina los datos de los dataset _cargoes_ y _passages_* genera dos nuevos datasets basados en el puerto de salida y el de llegada de la mercancía respectivamente, cada dataset contiene el sumatorio de kilos de mercancía para cada uno de los puertos  Código fuente  Librerías y funciones auxiliaresFunción `clean_port` recibe el nombre de un puerto (`port`) y un listado de reglas para corregir / hacer uniforme el nombre del puerto (`dct`). Si el puerto cumple alguna de las reglas, su nombre se sustituye por el nombre nombre asociado a la regla.Ejemplo:```port = 'Amst.'dct = {  'Amsterdam' : r'Amst.*'}fixed = clean_port(port, dct)print (fixed)> "Amsterdam"```Función `filter_amber` recibe una cadena de texto y decide si se corresponde con alguna de las posibles traducciones de la palabra "amber" soportadas por la función (actualmente soporta distintas versiones de la traducción danesa y neerlandesa).Ejemplo:```palabra = 'Barnsteen'filter_amber(palabra)> True```Función `geolocate_ports` toma una lista de nombres de puertos y devuelve un diccionario donde cada puerto tiene asociada las coordenadas _longitud_ y _latitud_. Si el código no puede encontrar las coordenadas para un determinado puerto, se le asignan las coordenadas `(None, None)`.Ejemplo:```puertos = ['Amsterdam', 'Hamburgo']coor = geolocate_ports(puertos)print(coor)> {>   'Amsterdam': [X1, Y1],>   'Hamburgo': [X2, Y2]> }```Función `fraction_to_float` recibe una variable, si es una cadena de texto que contiene algún tipo de fracción (e.g. `1 1/3`) la función calcula y devuelve el equivalente en formato `float`.Ejemplo:```var1 = 1.0var2 = '1 1/2'fraction_to_float(var1)> 1.0fraction_to_float(var2)> 1.5```<jupyter_code>import json
import re
import pandas as pd

from datetime import datetime
from fractions import Fraction
from geopy import geocoders  


def clean_port(port, dct):
    """
    Check a given port against the dictionary with name rules.
    Returns a corrected name if any rule applies
    
    :param port: port name, e.g. Ambsterdam
    :param dct: rule dictionary with naming regular expressions
    :returns: corrected port name based on rule dictionary
    """
    for key, regex in dct.items():
        if bool(re.search(regex, port)):
            return key
    return port


def filter_amber(x):
    """
    Check if string x matches the word "amber" in any of the
    supported languagles (Danish, Dutch and English)
    
    :param: x string
    :returns: True if it maches "amber" in Danish, Dutch or English
    """
    # rule for English
    re_amber = r'ambe'
    # rule for Danish and Dutch
    re_bersteen = r'b.rnste.*n'
    return bool(re.search(re_amber, x.lower())) or bool(re.search(re_bersteen, x.lower()))


def geolocate_ports(ports):
    """
    Calculate geographical coordinates for a list of cities (strings).
    
    :param ports: list of strings
    :returns: dictionary where each city (key) has associated its coordinates
              as a tuple. If a city is not found, coordinates are (None, None)
              
    :example:
        ports = ['Amsterdam', 'Rotterdam']
        coor = geolocate_ports(ports)
        print(coor)
        > {
        >   'Amsterdam': (x1, y1),
        >   'Rotterdam': (x2, y2)
        > }
    """
    gn = geocoders.Nominatim(user_agent='fakeusername')
    coordinates  = {}
    
    for port in ports:
        coor = gn.geocode(port)
        if coor is not None:
            coordinates[port] = (coor.latitude, coor.longitude)
        else:
            coordinates[port] = (None, None)
        
    return coordinates


def fraction_to_float(text):
    """
    Checks if a variable is a string containing a fraction expression
    (e.g. "1 1/2"). If so it calculates the equivalent float value.
    
    :param text: value to check
    :returns: float value
    """
    if isinstance(text, str):
        total = [Fraction(part) for part in text.split(' ')]
        return float(sum(total))
    else:
        return text<jupyter_output><empty_output><jupyter_text>Archivos CSV y campos asociadosLos datos obtenidos de _Soundtoll Database_ están ubicados en el directorio `data/`.Las cabeceras de los archivos CSV están en neerlandés, las variables `fields_cargo` y `fields_passage` contiene las traducciones de estas cabeceras al inglés.<jupyter_code># CSV files to be processed
file_cargo = 'data/cargoes_Advanced_search_results_values_B%rnste%n__1634_1857_.csv'
file_passage = 'data/passages_Advanced_search_results_values_B%rnste%n__1634_1857_.csv'

# translation NL-EN for cargo headers
fields_cargo = {
    'id_doorvaart': 'id',
    'van': 'from',
    'naar': 'to',
    'maat': 'unit', 
    'aantal': 'amount',
    'soort': 'type'
}
# list of NL cargo headers only
fields_cargo_nl = fields_cargo.keys()

# translation NL-EN for passage headers
fields_passage = {
    'id_doorvaart': 'id',
    'dag': 'day',
    'maand': 'month',
    'jaar': 'year'
}
# list of NL passage headers only
fields_passage_nl = fields_passage<jupyter_output><empty_output><jupyter_text>Nombres de puertosEl código permite filtrar los registros de carga asociados con puertos o destinos que no están soportados por algún motivo: nombres no se corresponde con un puerto (e.g. "Francia"), o no ha sido definido.Los puertos soportados presentan cierta variabilidad en su ortografía (e.g. nombres para Londres: London, Lundon, ..). El código usa expresiones regulares para identificar las distintas variantes de un nombre de puerto y reemplazarla por su nombre _canónico_. Las reglas han sido definidas a mano después de explorar manualmente los nombres de puertos existentes en los conjuntos de datos. Añadir o modificar reglas es trivial, sólo requiere conocimiento básico sobre sintaxis de expresiones regulares.<jupyter_code># port names to be removed
ports_remove = ['-', 'Østersøen', 'Franckeriige', '<NAME>', 'Westerwig', 'Der Liebau', 'Liebau', 'Norge']

ports_regex = {
    'Amsterdam': r'Amb?s.*',
    'Konigsberg': r'Cønn?i.+|K.nn?i?s?.+',
    'Danzig': r'Dan.+',
    'Dunkirk': r'D.nk.rchen',
    'Emden': r'Emb?den',
    'Gottenborg': r'Got.enborg',
    '<NAME>': r'<NAME>.s',
    'Helsingør': r'Helsingø.?r',
    'Hull': r'Hul?',
    'Copenhagen': r'K.øbenha..',
    'Landskrona': r'Landscrona',
    'Leeuwarden': r'L.ur?w.+en',
    'London': r'L[o|u]nn?d',
    'Montrose': r'Montrosse?',
    'Newcastle': r'Nycast.*',
    'Petersburg': r'Petersb.rg',
    'Rugenwalde': r'R.genwalde',
    'Stockholm': r'Stock.*',
    'Vedbæk': r'Wedbek',
    'Dundee': r'Dundie',
    'Trondheim': r'Tronhiem',
    'Ramsgate': r'Romansgate',
    'Klaipėda': r'<NAME>',
}<jupyter_output><empty_output><jupyter_text>Tabla de conversión de unidades locales de medida a KilogramosLos conjuntos de dato de entrada usan unidades de medida usadas en el momento del registro de los datos. Algunas unidades de medida son de masa y tienen equivalentes en kilos. Otras no tienen un equivalente en unidades de masa (e.g. "docenas").Las unidades soportadas por el código son:* _pund_* _skippund_* _lispund_* _centnerEl diccionario `unit_to_kg` recoge todas las unidades soportadas y su equivalencia en Kg.<jupyter_code>unit_to_kg = {
    # https://www.sizes.com/units/pund.htm
    'Pund': 0.5,
    # https://www.sizes.com/units/skippund.htm
    'Skippund': 160.076,
    # https://sv.wikipedia.org/wiki/Lispund
    # https://www.sizes.com/units/lispund.htm
    'Lispund': 8.003,
    # https://www.sizes.com/units/centner.htm
    'Centner': 100,
}<jupyter_output><empty_output><jupyter_text>Cargar datos desde archivos CSV<jupyter_code># cargoes
cargoes_raw = pd.read_csv(file_cargo, header=0, usecols=fields_cargo_nl, sep=';')
# translate headers
cargoes_raw = cargoes_raw.rename(columns=fields_cargo)

# passages
passages_raw = pd.read_csv(file_passage, header=0, usecols=fields_passage_nl, sep=';')
# translate headers
passages_raw = passages_raw.rename(columns=fields_passage)<jupyter_output><empty_output><jupyter_text>Construir campo fechaUnimos los campos `year`, `month` y `day` en un único campo `date` (Fecha).<jupyter_code># create new column date combining several columns
passages_raw['date'] = passages_raw[['year', 'month', 'day']].apply(
    lambda x: datetime(x[0], x[1], x[2]),
    axis=1
)
# remove columns day, month and year
passages = passages_raw.drop(columns=['day', 'month', 'year'])<jupyter_output><empty_output><jupyter_text>Seleccionar carga de tipo "ámbar"Filtramos el dataset `cargoes_raw` seleccionando únicamente las filas del tipo _ámbar_. La función `filter_amber` permite determinar si una cadena de texto se corresponde con la mercancía _ámbar_ en alguno de los idiomas soportados (danés, inglés y neerlandés).<jupyter_code># logic vector, indicates if few is "amber" type or not
filter_vector = cargoes_raw['type'].apply(filter_amber)
# keep only rows of "amber" type
cargoes_raw = cargoes_raw[filter_vector]<jupyter_output><empty_output><jupyter_text>Procesar nombres de puertosAplicamos las reglas definidas en `ports_regex` para hacer uniforme los nombres de los puertos. Aplicamos estas reglas tanto a puertos de origen (`port_from`) como a los de destino (`port_to`).<jupyter_code>cargoes_raw['port_from'] = cargoes_raw[['from']].apply(lambda x: clean_port(x[0], ports_regex), axis=1)
cargoes_raw['port_to'] = cargoes_raw[['to']].apply(lambda x: clean_port(x[0], ports_regex), axis=1)
# remove original columns, not needed any more
cargoes = cargoes_raw.drop(['from', 'to'], axis=1)<jupyter_output><empty_output><jupyter_text>Filtrar unidades de medida soportadasEliminamos todos los registros asociados a unidades de medida no contempladas en el diccionario `unit_to_kg`.<jupyter_code>cargoes = cargoes[cargoes['unit'].isin(unit_to_kg.keys())]<jupyter_output><empty_output><jupyter_text>Filtrar puertos no soportadosEliminados los puertos que queremos excluir del análisis (`ports_remove`).<jupyter_code>cargoes = cargoes[~cargoes['port_from'].isin(ports_remove)]
cargoes = cargoes[~cargoes['port_to'].isin(ports_remove)]<jupyter_output><empty_output><jupyter_text>Procesar fraccionesAlgunas cantidades están expresadas como fracciones (i.e. '1 3/4'). Convertimos esta representación a formato decimal.<jupyter_code># from fractions to float
cargoes['amount'] = cargoes['amount'].apply(fraction_to_float)
cargoes['kg'] = cargoes[['amount', 'unit']].apply(lambda x: x[0] * unit_to_kg[x[1]], axis=1)<jupyter_output><empty_output><jupyter_text>Agrupar datos por viajeAlgunos viajes presentan varias entradas para la mercancía _ámbar_ en distintas unidades de medida. Una vez que todas las unidades de medida han sido convertidas a kilogramos, es posible sumar estas cantidades, obteniendo el total de kilogramos transportados en un determinado viaje<jupyter_code># group by id, port_from and port_to, calculate sum of kg
# original units and amounts will be lost during the aggregation
cargoes_summary = cargoes.groupby(['id', 'port_from', 'port_to'])['kg'].sum()
# it is required to reset indexes
cargoes_summary = cargoes_summary.reset_index()<jupyter_output><empty_output><jupyter_text>Calcular / cargar coordenadasCálculo de coordenadas puede ser lento. Proporcionamos un diccionario precalculado.Re-calcular coordenadas sólo requiere asignar valor `False` a la variable `use_coordinates_cache`, pero puede llegar alrededor de 1 minuto. En caso de que nuevos puertos sean añadidos al conjunto de datos, será necesario volver a calcular todas las coordenadas geográficas.<jupyter_code># list of all the processed port names in cargoes dataset
ports_filtered = set.union(set(cargoes['port_from'].unique()), set(cargoes['port_to'].unique()))

# change this if you need to fetch again all coordinates!
use_coordinates_cache = True

if use_coordinates_cache:
    # load coordinates cache
    with open('./coordinates.json', 'r') as file_json:
        coordinates = json.load(file_json)
else:
    coordinates = geolocate_ports(list(ports_filtered))
    with open('./coordinates.json', 'w') as file_json:
        json.dump(coordinates, file_json)

cargoes_summary['lat_from'] = cargoes_summary['port_from'].apply(lambda x: coordinates[x][0])
cargoes_summary['lon_from'] = cargoes_summary['port_from'].apply(lambda x: coordinates[x][1])

cargoes_summary['lat_to'] = cargoes_summary['port_to'].apply(lambda x: coordinates[x][0])
cargoes_summary['lon_to'] = cargoes_summary['port_to'].apply(lambda x: coordinates[x][1])<jupyter_output><empty_output><jupyter_text>Generar dataset finalUnimos los datos procesados de _cargoes_ y _passages_ en una única tabla.<jupyter_code>table = pd.merge(cargoes_summary, passages, on='id')
table.to_csv('amber.csv')
table<jupyter_output><empty_output><jupyter_text>Crear resúmenes del total de mercancíaCreamos dos conjunto de datos distintos, uno para ámbar recibido por cada puerto y otro para ámbar enviado desde cada puerto. Estos conjuntos de datos serán usados en QGis para crear capas mostrando la cantidad total de ámbar saliendo y llegando a cada uno de los puertos.<jupyter_code>port_from = table.groupby(['port_from', 'lat_from', 'lon_from'])['kg'].sum()
port_from = port_from.reset_index()
port_from.to_csv('amber_port_origin.csv')

port_to = table.groupby(['port_to', 'lat_to', 'lon_to'])['kg'].sum()
port_to = port_to.reset_index()
port_to.to_csv('amber_port_destination.csv')
port_from
port_to<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10389173175329922, 'fraction_numerical': 0.006329113924050633, 'mean_word_length': 5.05010183299389, 'pattern_counts': {'":': 0, '<': 66, '<?xml version=': 0, '>': 78, 'https://': 6, 'lorem ipsum': 0, 'www.': 5, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '432386', 'license': 'CC0-1.0', 'n_tokens_mistral': 5212, 'n_tokens_neox': 5085, 'n_words': 1521, 'path': 'notebook.ipynb', 'repo_name': 'juanlufont/hhdd-mrve', 'size': '14890'}