Grafici delle Serie Temporali con Dash, Flask, TimescaleDB e Docker – Parte 2

scienzadeidati articoli - Grafici delle Serie Temporali con Dash, Flask, TimescaleDB, and Docker - Parte 2

Questo è il secondo di tre articoli su TimescaleDB, Flask e Dash. Il primo articolo si è concentrato su come installare e configurare il database TimescaleDB tramite l’esecuzione in Docker, inoltre abbiamo configurato PgAdmin per la gestione del database. Questo articolo si concentra sul linguaggio Python, sulla creazione di un sito Web con Flask e quindi sull’integrazione del framework Web Dash all’interno di Flask.

Flask è un framework web Python molto popolare, leggero ed estensibile, quindi è molto adatto per applicazioni di data science. Dash è un framework Web basato su Flask, che utilizza React JavaScript dietro le quinte per creare applicazioni reattive e interattive a pagina singola (SPA), utilizzando i grafici generati con la libreria Plotly . Direi che Dash sta per Python come Shiny sta per R , in quanto entrambi si concentrano sulla produzione di data science e modelli di machine learning, senza che un utente debba imparare molto HTML, CSS e JavaScript. In genere i data scientist non sono ingegneri del software e le complessità della creazione di un’applicazione web a pagina singola sono troppo complicate e non valgono il loro tempo.

Questo tutorial mostra come ottenere il meglio da alcuni mondi diversi:

  1. Un’applicazione Flask per un normale sito web
  2. Un’elegante applicazione Dash a pagina singola che utilizza il meglio di React JavaScript
  3. Un modo per produrre un’applicazione di data science

Cominciamo con una semplice applicazione web Flask e quindi vediamo come integrare Dash. La parte 3 di questa serie approfondirà la creazione di grafici interattivi con ​​Dash.

Tutto il codice per questo tutorial può essere trovato qui su GitHub.

Parte 2 - Integrazione dei Framework Web Flask e Dash

Prima di iniziare un nuovo progetto con Python, dobbiamo sempre creare un ambiente virtuale Python3 dedicato. Usiamo solo python3 -m venv venv per creare un ambiente virtuale chiamato “venv” nella cartella principale del progetto. In questi giorni preferisco usare Poetry rispetto a Pip , ma Poetry non è al centro di questo articolo. Ora attiviamo l’ambiente virtuale con source venv/bin/activate su Linux / Mac o venv\Scripts\activate.bat su Windows. Dopo aver attivato l’ambiente virtuale, installiamo Flask, Dash, Dash Bootstrap Components e la libreria PostgreSQL psycopg2, con pip install flask dash dash-bootstrap-components psycopg2-binary.

Per creare un’applicazione Flask, iniziamo dal punto di ingresso più esterno o dal punto di partenza dell’applicazione. Nella cartella di primo livello, creiamo un wsgi.py file come segue. Questa è la pratica migliore, usando factory pattern per inizializzare Flask.

            # wsgi.py

from app import create_app

app = create_app()

        

Nel file wsgi.py si importando la funzione chiamata create_app presente all’interno di un pacchetto/libreria chiamato app, quindi creiamo una cartella app per ospitare la nostra applicazione Flask. All’interno della cartella dell’app, come per tutti i pacchetti Python, creiamo un file __init__.py:

            # /app/__init__.py

import os
import logging

# Third-party imports
from flask import Flask, render_template

# Local imports
from app import database
from app.dash_setup import register_dashapps


def create_app():
    """Factory function that creates the Flask app"""

    app = Flask(__name__)
    app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
    logging.basicConfig(level=logging.DEBUG)

    @app.route('/')
    def home():
        """Our only non-Dash route, to demonstrate that Flask can be used normally"""
        return render_template('index.html')

    # Initialize extensions
    database.init_app(app) # PostgreSQL db with psycopg2

    # For the Dash app
    register_dashapps(app)

    return app


        

Il file contiene la funzione create_app() necessaria  al file precedente  wsgi.py. Per ora possiamo ignorare le altre importazioni locali: ci ritorneremo tra poco.

All’interno della fuzione create_app(), iniziamo con le basi, istanziando l’istanza Flask() passandole il __name__ del file e impostando la SECRET_KEY… Chiave segreta?

Apriamo il file .env e aggiungiamo una variabile di ambiente SECRET_KEY in fondo al file, insieme alle altre variabili di ambiente:

            # .env

# For the Postgres/TimescaleDB database. 
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_HOST=timescale
POSTGRES_PORT=5432
POSTGRES_DB=postgres
PGDATA=/var/lib/postgresql/data

# For the PGAdmin web app
[email protected]
PGADMIN_DEFAULT_PASSWORD=password

# For Flask
SECRET_KEY=long-random-string-of-characters-numbers-etc-must-be-unique # NEW

        
Tornando al nostro file __init__.py, impostiamo la registrazione di base e quindi aggiungiamo la prima “route” di Flask (la homepage principale), solo per dimostrare che abbiamo una normale applicazione Flask funzionante. Creiamo una cartella /app/templates per i modelli HTML e aggiungiamo un file index.html con i seguenti contenuti per la nostra home page:
            <html>
    <body>
        <h1 style="text-align: center;">
            Click <a href="/dash/">here</a> to see the Dash single-page application (SPA)
        </h1>
    </body>
</html>
        

Successivamente, inizializziamo il nostro database con database.init_app(app). “Database” è un modulo locale che abbiamo importato all’inizio, quindi vediamo come implementarlo.

Infine, in fondo alla funzione create_app(), richiamiamo la funzione register_dashapps(app) dal modulo dash_setup.py. Qui è dove inizializzeremo l’applicazione web Dash che utilizza il motore React JavaScript sotto il cofano. 

Connessione al Database

Creiamo il file database.py, accanto al file __init__.py. Seguiamo le best practice consigliate qui dal team di Flask. Lo scopo di questo modulo è creare alcune funzioni per aprire e chiudere una connessione al database TimescaleDB e garantire che la connessione venga chiusa da Flask alla fine della richiesta HTTP. La funzione “init_app (app)” è quella che viene richiamata nel file __init__.py attraverso la funzione create_app(). Da notare che teardown_appcontext(close_db) assicura che la connessione venga chiusa durante lo “smontaggio”. In futuro, quando avremo bisogno di dati dal database, richiameremo semplicemente get_conn() per ottenere una connessione al database ed eseguire le query SQL.

Nel caso ve lo stiate chiedendo, g è fondamentalmente un oggetto globale in cui memorizzate la connessione al database. È complicato, quindi non me ne parlerò, sappi solo che questa è la pratica migliore e goditi la vita. 😉 Va bene, ecco un link per ulteriori letture…

Se stai cercando un ottimo corso su Flask, che includa un’immersione profonda nella meccanica di Flask, consiglio vivamente questo corso di Patrick Kennedy.

            # /app/database.py


import os
import psycopg2
from flask import g


def get_conn():
    """
    Connect to the application's configured database. The connection
    is unique for each request and will be reused if this is called
    again.
    """
    if 'conn' not in g:
        g.conn = psycopg2.connect(
            host=os.getenv('POSTGRES_HOST'),
            port=os.getenv("POSTGRES_PORT"), 
            dbname=os.getenv("POSTGRES_DB"), 
            user=os.getenv("POSTGRES_USER"), 
            password=os.getenv("POSTGRES_PASSWORD"), 
            connect_timeout=5
        )
    
    return g.conn


def close_db(e=None):
    """
    If this request connected to the database, close the
    connection.
    """
    conn = g.pop('conn', None)

    if conn is not None:
        conn.close()
    
    return None


def init_app(app):
    """
    Register database functions with the Flask app. This is called by
    the application factory.
    """
    app.teardown_appcontext(close_db)

        

Integrazione con Dash

Passiamo ora a descrivere il modulo il dash_setup, dove è definita la funzione register_dashapps. Creiamo un file chiamato dash_setup.pyall’interno della cartella “/app”, accanto a __init__.py:

            # /app/dash_setup.py

import dash
from flask.helpers import get_root_path


def register_dashapps(app):
    """
    Register Dash apps with the Flask app
    """

    # external CSS stylesheets
    external_stylesheets = [
        'https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css'
    ]
    
    # external JavaScript files
    external_scripts = [
        "https://code.jquery.com/jquery-3.5.1.slim.min.js",
        "https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js",
        "https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js",
    ]

    # To ensure proper rendering and touch zooming for all devices, add the responsive viewport meta tag
    meta_viewport = [{
        "name": "viewport", 
        "content": "width=device-width, initial-scale=1, shrink-to-fit=no"
    }]

    dashapp = dash.Dash(
        __name__,
        # This is where the Flask app gets appointed as the server for the Dash app
        server = app,
        url_base_pathname = '/dash/',
        # Separate assets folder in "static_dash" (optional)
        assets_folder = get_root_path(__name__) + '/static_dash/', 
        meta_tags = meta_viewport, 
        external_scripts = external_scripts,
        external_stylesheets = external_stylesheets
    )
    dashapp.title = 'Dash Charts in Single-Page Application'
    
    # Some of these imports should be inside this function so that other Flask
    # stuff gets loaded first, since some of the below imports reference the other
    # Flask stuff, creating circular references
    from app.dashapp.layout import get_layout
    from app.dashapp.callbacks import register_callbacks

    with app.app_context():

        # Assign the get_layout function without calling it yet
        dashapp.layout = get_layout

        # Register callbacks
        # Layout must be assigned above, before callbacks
        register_callbacks(dashapp)

    return None

        

La funzione  register_dashapps(app) viene passata all’istanza dell’applicazione Flask, che Dash assegnerà come “server”.

Per prima cosa, creeremo una sorta di template HTML passando alla classe dash.Dash()alcuni fogli di stile e script. Questi file .js e .css si trovano nella sezione “head” dei file HTML, quindi Dash li metterà lì per noi.

Useremo Bootstrap CSS per rendere eccezionale la nostra applicazione a pagina singola e funzionare alla grande sui telefoni cellulari. Bootstrap 4 richiede anche Popper e jQuery, quindi li includiamo secondo le linee guida per l’installazione di Bootstrap, disponibile qui.

Dopo aver inizializzato l’istanza Dash (dashapp), creeremo il suo layout HTML / CSS e i callback JavaScript (abbiamo React dietro le quinte).

Per evitare riferimenti circolari, importiamo il layout e i moduli di callback all’interno della funzione register_dashapps(app).

Per fare questo dobbiamo creare una cartella “dashapp” all’interno della cartella “app”, contenente tre nuovi file:

  1. __init__.py
  2. callbacks.py
  3. layout.py

Non preoccupiamoci per il file __init__.py: è lì, quindi Python (e tu) sa che la cartella fa parte del pacchetto del nostro progetto.

La parte 3 approfondisce il layout e i callback di Dash, quindi per ora impostiamo solo le basi.

Innanzitutto, layout.py conterrà per ora solo una barra di navigazione Bootstrap all’interno di un contenitore Bootstrap:

            # /app/dashapp/layout.py

import os

from flask import url_for
import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
import psycopg2
from psycopg2.extras import RealDictCursor

# Local imports
from app.database import get_conn


def get_navbar():
    """Get a Bootstrap 4 navigation bar for our single-page application's HTML layout"""

    return dbc.NavbarSimple(
        children=[
            dbc.NavItem(dbc.NavLink("Blog", href="https://mccarthysean.dev")),
            dbc.NavItem(dbc.NavLink("IJACK", href="https://myijack.com")),
            dbc.DropdownMenu(
                children=[
                    dbc.DropdownMenuItem("References", header=True),
                    dbc.DropdownMenuItem("Dash", href="https://dash.plotly.com/"),
                    dbc.DropdownMenuItem("Dash Bootstrap Components", href="https://dash-bootstrap-components.opensource.faculty.ai/"),
                    dbc.DropdownMenuItem("Testdriven", href="https://testdriven.io/"),
                ],
                nav=True,
                in_navbar=True,
                label="Links",
            ),
        ],
        brand="Home",
        brand_href="/",
        color="dark",
        dark=True,
    )


def get_layout():
    """Function to get Dash's "HTML" layout"""

    # A Bootstrap 4 container holds the rest of the layout
    return dbc.Container(
        [
            # Just the navigation bar at the top for now...
            # Stay tuned for part 3!
            get_navbar(),
        ], 
    )

        

La funzione get_layout() viene richiamata dal nostro modulo dash_setup.py.

Non descriverò la funzione get_navbar() perché penso sia autoesplicativa, ma se vuoi approfondire in questo link trovi la documentazione.

I callback di Dash sono il fulcro della parte 3 di questa serie, quindi per ora implementiamo il file callbacks.py con quanto segue:

            # /app/dashapp/callbacks.py

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State


def register_callbacks(dash_app):
    """Register the callback functions for the Dash app, within the Flask app"""        

    return None

        

Siamo arrivati alla conclusione di questa seconda parte. Spero che finora ti sia piaciuta. Dai un’occhiata alla Parte 3 per un’analisi approfondita dei callback di Dash e dei grafici di Plotly.

A presto!

Grafici delle Serie Temporali con Dash, Flask, TimescaleDB e Docker – Parte 1

scienzadeidati articoli - Grafici delle Serie Temporali con Dash, Flask, TimescaleDB, and Docker - Parte 01

In questo tutorial in tre parti, ti mostrerò come creare un’applicazione reattiva a pagina singola interamente in Python, con grafici di serie temporali dinamici tramite Dash / Plotly, il framework Flask per siti Web e un database specializzato per le serie temporali chiamato TimescaleDB, che a sua volta è basato su PostgreSQL. Un bel boccone, ma questo è uno stack tecnologico piuttosto interessante che è facile da imparare e da programmare, poiché utilizza solo Python e SQL standard (senza JavaScript). Quindi è uno stack ideale per distribuire rapidamente applicazioni di data science.

La prima parte del tutorial si concentrerà sull’utilizzo di Docker per configurare il database TimescaleDB specializzato e PGAdmin per gestirlo. Creeremo alcuni dati IoT simulati e mostreremo alcune delle fantastiche funzionalità di TimescaleDB, che non troverai nel PostgreSQL standard.

La seconda parte si concentrerà sulla configurazione di un sito Web Python Flask che si integra con la straordinaria libreria Dash per la creazione di applicazioni a pagina singola (SPA) basate su JavaScript React. Ti mostrerò come integrare correttamente Dash in Flask, in modo da poter ottenere il meglio da entrambi i framework web.

La terza parte si concentrerà sull’utilizzo di Dash per creare grafici di serie temporali interattivi per il monitoraggio dei dati IoT o per mostrare la tua applicazione di data science.

Tutto il codice per questo tutorial può essere trovato qui su GitHub.

Uso Docker laddove possibile, per ambienti riproducibili e una distribuzione estremamente semplice utilizzando Docker Swarm, quindi se non hai familiarità con Docker, consulta la documentazione qui.

Parte 1 - TimescaleDB, PGAdmin e Docker

Per prima cosa, creiamo una rete Docker in modo che i nostri prossimi contenitori possano parlare tra loro:

            docker network create --attachable --driver bridge timescale_network
        
Successivamente, creiamo un database TimescaleDB locale utilizzando Docker-Compose. Questo avvierà rapidamente un database PostgreSQL locale con l’estensione TimescaleDB configurata automaticamente. Si usa il seguente file docker-compose.timescale.yml:
            # docker-compose.timescale.yml

version: '3.7'
services:
  timescale:
    image: timescale/timescaledb:1.7.4-pg12
    container_name: flaskapp_timescale
    volumes: 
      - type: volume
        # source: timescale-db # the volume name
        source: timescale_volume
        # target: the location in the container where the data are stored
        target: /var/lib/postgresql/data 
        read_only: false
      # Custom postgresql.conf file will be mounted (see command: as well)
      - type: bind
        source: ./postgresql_custom.conf
        target: /postgresql_custom.conf
        read_only: false
    env_file: .env
    environment: 
      POSTGRES_HOST: timescale
    command: ["-c", "config_file=/postgresql_custom.conf"]
    ports: 
      - 0.0.0.0:5432:5432
    networks:
      timescale_network:
    deploy:
      restart_policy:
        condition: on-failure

# Creates a named volume to persist our database data
volumes:
  timescale_volume:

# Joins our external network
networks:
  timescale_network:
    external: true
        

Nel precedente file Docker-Compose possiamo notare i seguenti aspetti:

  • Si utilizza il  timescale_network che abbiamo creato nel passaggio precedente.
  • Si utilizza un volume per rendere persistenti i dati del database, anche se il contenitore Docker viene rimosso o sostituito. Questo è molto comune per i database “Dockerizzati”.
  • Si utilizza la porta 5432 (questo sarà importante quando proveremo ad accedere al database).
  • Si utilizza un file di configurazione personalizzato e un file  .env per memorizzare le informazioni sensibili per la connessione al database, come la password del database. E’ quindi necessario creare questi due file.

Ecco il file di configurazione personalizzato, nel caso in cui desideri / devi modificare una di queste impostazioni in futuro. Il file è troppo lungo per essere inserito in un blocco di codice in questo articolo, quindi fai clic su questo collegamento , quindi copia e incolla il testo in un file chiamato postgresql_custom.conf e mettilo nella radice della cartella del progetto.

Successivamente, ecco un modello per il file .env, che puoi lasciare nella root della cartella del tuo progetto, insieme ai file Docker-Compose e a quelli di configurazione del database:

            # .env

# For the Postgres/TimescaleDB database. 
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_HOST=timescale
POSTGRES_PORT=5432
POSTGRES_DB=postgres
PGDATA=/var/lib/postgresql/data
        

Ora che abbiamo aggiunto la configurazione personalizzata e i file .env, puoi avviare il database TimescaleDB con il seguente comando. Il -d esegue il comando in background (--detached).

            docker-compose -f docker-compose.timescale.yml up -d
        
Controlla i tuoi contenitori in esecuzione con docker container ls o la vecchia scuola docker ps. Se il contenitore si sta riavviando, controlla i log con docker logs <container id> e assicurati di aver configurato il file .env, il file di configurazione e la rete Docker da cui dipende. Infine, creiamo un ambiente PGAdmin amichevole per l’amministrazione del nostro database e l’esecuzione di SQL. Crea un file chiamato docker-compose.pgadmin.yml e aggiungi quanto segue:
            # docker-compose.pgadmin.yml

version: '3.7'
services:
  pgadmin:
    # Name of the container this service creates. Otherwise it's prefixed with the git repo name
    image: "dpage/pgadmin4:latest"
    container_name: flaskapp_pgadmin4
    restart: unless-stopped
    env_file: .env
    environment: 
      PGADMIN_LISTEN_PORT: 9000
    ports: 
      - 0.0.0.0:9000:9000
    volumes: 
      # So the database server settings get saved and stored even if the container is replaced or deleted
      - pgadmin:/var/lib/pgadmin
    networks:
      timescale_network:

volumes:
  pgadmin:

networks:
  timescale_network:
    external: true

        
Aggiungi le seguenti righe al tuo .env file per PGAdmin. Avrai bisogno di queste informazioni di accesso quando tenterai di accedere a PGAdmin nel browser web.
            # .env

# For the PGAdmin web app
[email protected]
PGADMIN_DEFAULT_PASSWORD=password
        

Avvia l’applicazione web PGAdmin (PostgreSQL Admin) con il seguente comando Docker:

            docker-compose -f docker-compose.pgadmin.yml up -d

        

Esegui di docker container lsnuovo per verificare se il contenitore PGAdmin è in esecuzione. Nota che abbiamo specificato una porta di 9000, quindi ora puoi accedere a PGAdmin su http://localhost:9000 o http://127.0.0.1:9000 .Accedi con il nome utente e la password che hai impostato nel tuo file .env.

Ora che hai effettuato l’accesso a PGAdmin, fai clic con il pulsante destro del mouse su “Server” e “Crea / Server…”. Chiamalo “TimescaleDB Locale” nella scheda “Generale” e digita quanto segue nella scheda “Connessione”:

  • Host : timescale (questo è il nome host del “Servizio” Docker definito nel primo file docker-compose.yml per il contenitore del database TimescaleDB)
  • Porta : 5432
  • Database di manutenzione : postgres
  • Nome utente : postgres
  • Password : password

Fai clic su “Salva” e dovresti essere connesso. Ora puoi fare doppio clic su “TimescaleDB Local” e puoi accedere alle tabelle del tuo database su “/ Databases / postgres / Schemas / public / Tables”. Molto intuitivo, eh? Nel menu “Strumenti”, fai clic su “Strumento di query” e sei pronto per iniziare a scrivere SQL.

Ora sei l’orgoglioso comandante di un database TimescaleDB, che è identico a un database PostgreSQL (“Il database open source più avanzato al mondo”, se credi nel loro marketing), tranne per il fatto che ora ha capacità speciali per gestire dati ad alta frequenza di serie temporali.

I dati delle serie temporali sono leggermente diversi dai normali dati relazionali per descrivere utenti e cose. I dati delle serie temporali possono arrivare in qualsiasi secondo o anche più volte al secondo, a seconda di ciò che si sta archiviando, quindi il database deve essere in grado di gestire molti inserimenti. Alcuni esempi sono i dati finanziari, come i prezzi di negoziazione del mercato azionario o dati di Internet of Things (IoT), solitamente per il monitoraggio di metriche ambientali come temperatura, pressione, umidità o qualsiasi altra cosa tu possa pensare. Di solito quando esegui query sui dati di serie temporali, sei interessato ai dati più recenti e di solito stai filtrando sulla colonna timestamp, quindi è assolutamente necessario indicizzarli. TimescaleDB è specializzato in questo genere di cose.

Creiamo uno speciale “Hypertable” di TimescaleDB e inseriamo alcuni dati con cui giocare. Ecco la documentazione . Ed ecco il tutorial da cui ottengo i dati SQL simulati.

In PGAdmin, se non ci sei già, nel menu “Strumenti”, fai clic su “Strumento di query” e digita il seguente SQL per creare due tabelle di dati IoT:

            CREATE TABLE sensors(
  id SERIAL PRIMARY KEY,
  type VARCHAR(50),
  location VARCHAR(50)
);

CREATE TABLE sensor_data (
  time TIMESTAMPTZ NOT NULL,
  sensor_id INTEGER,
  temperature DOUBLE PRECISION,
  cpu DOUBLE PRECISION,
  FOREIGN KEY (sensor_id) REFERENCES sensors (id)
);

CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE


        

Ora vediamo una funzionalità speciale di TimeScaleDB che non puoi fare in un normale database PostgreSQL. Trasformeremo la tabella sensor_data  in una “Hypertable”. Dietro le quinte, TimescaleDB partizionerà i dati sulla dimensione temporale, semplificando il filtraggio, l’indicizzazione e l’eliminazione dei vecchi dati delle serie temporali.

Se sei arrivato a questo tutorial per sfruttare le caratteristiche uniche di TimescaleDB, di seguito vedrai dove avviene la magia.

Eseguiamo la seguente query in PGAdmin per creare l’hypertable, partizionato automaticamente sulla dimensione “time”:

            SELECT create_hypertable('sensor_data', 'time');

        

Ora che è stata creata la nostra tabella di serie temporali specializzata, creiamo un indice speciale sull’ID del sensore, poiché è molto probabile che filtreremo sia l’ID del sensore che l’ora.

            create index on sensor_data (sensor_id, time desc);
        

Aggiungiamo ora alcuni dati di esempio nella tabella “sensori”:

            INSERT INTO sensors (type, location) VALUES
    ('a','floor'),
    ('a', 'ceiling'),
    ('b','floor'),
    ('b', 'ceiling');

        

Ora la parte divertente: creiamo alcuni dati simulati per le serie temporali:

            INSERT INTO sensor_data 
  (time, sensor_id, cpu, temperature)
SELECT
  time,
  sensor_id,
  random() AS cpu,
  random()*100 AS temperature
FROM 
  generate_series(
    now() - interval '31 days', 
    now(), interval '5 minute'
  ) AS g1(time), 
  generate_series(1,4,1) AS g2(sensor_id);

        

Eseguiamo una semplice query di selezione per vedere alcuni dei nostri dati appena simulati:

            SELECT * 
FROM sensor_data
WHERE time > (now() - interval '1 day')
ORDER BY time;
        

Ecco un altro esempio di selezione dei dati aggregati (ovvero la media ad 1 ora, invece di vedere ogni singolo punto dati):

            SELECT 
  sensor_id,
  time_bucket('1 hour', time) AS period, 
  AVG(temperature) AS avg_temp, 
  AVG(cpu) AS avg_cpu 
FROM sensor_data 
GROUP BY 
  sensor_id, 
  time_bucket('1 hour', time)
ORDER BY 
  sensor_id, 
  time_bucket('1 hour', time);

        

Dal tutorial ufficiale di TimescaleDB, mostriamo altre due query. Innanzitutto, invece di una cronologia delle serie temporali, potremmo semplicemente desiderare i dati più recenti . Per questo, possiamo utilizzare la funzione “last()”:

            SELECT 
  time_bucket('30 minutes', time) AS period, 
  AVG(temperature) AS avg_temp, 
  last(temperature, time) AS last_temp --the latest value
FROM sensor_data 
GROUP BY period;

        

E, naturalmente, spesso vogliamo unire i dati delle serie temporali con i metadati (cioè i dati sui dati). In altre parole, otteniamo una posizione per ogni sensore, piuttosto che un ID sensore:

            SELECT 
  t2.location, --from the second metadata table
  time_bucket('30 minutes', time) AS period, 
  AVG(temperature) AS avg_temp, 
  last(temperature, time) AS last_temp, 
  AVG(cpu) AS avg_cpu 
FROM sensor_data t1 
INNER JOIN sensors t2 
  on t1.sensor_id = t2.id
GROUP BY 
  period, 
  t2.location;


        

TimescaleDB ha un’altra funzione molto utile chiamata “aggregazioni continue” per aggiornare continuamente ed efficientemente le visualizzazioni aggregate dei dati delle nostre serie temporali. Se desideriamo creare rapporti / grafici sui dati aggregati, il seguente codice per la creazione di viste fa al nostro caso:

            CREATE VIEW sensor_data_1_hour_view
WITH (timescaledb.continuous) AS --TimescaleDB continuous aggregate
SELECT 
  sensor_id,
  time_bucket('01:00:00'::interval, sensor_data.time) AS time,
  AVG(temperature) AS avg_temp, 
  AVG(cpu) AS avg_cpu
FROM sensor_data
GROUP BY 
  sensor_id,
  time_bucket('01:00:00'::interval, sensor_data.time)


        

Questo è tutto per la parte 1 di questo tutorial in tre parti su TimescaleDB, Dash e Flask. Ecco la parte 2 sull’integrazione di Dash e Flask. La parte 3 si concentrerà sulla creazione di grafici di serie temporali reattivi e interattivi in ​​Dash per la tua applicazione a pagina singola (SPA).

A presto!

Come manipolare date e ore in Python

scienzadeidati articoli - Python Datetime

Quando si usa Python, arriva sempre il momento di capire come “manipolare” i dati temporali e in particolare la data e l’ora.

In questo articolo descriviamo le principali funzioni relative a l modulo DateTime, già compreso nell’installazione base di Python. Se cerchi qualcosa di semplice ma pratico con esempi, continua a leggere!

In particolare vediamo tutti e 7 i seguenti argomenti più popolari:

  • Data e ora di oggi in diversi formati
  • Conversione da stringa a data
  • La differenza nel calcolo di DateTime
  • Datetime più/meno un’ora specifica
  • Confronto data/ora
  • Impostazioni fusi orari
  • Unix timestamp / calcolo del tempo dell’epoca

 

Data e ora di oggi in diversi formati

Vediamo la manipolazione più semplice. Di seguito è riportato il codice che stampa l’anno, il mese, la data, l’ora, i minuti, i secondi e i millisecondi correnti.

Queste sono informazioni utili, ma spesso ne abbiamo bisogno solo in parte. Possiamo stampare le diverse parti di seguito.
            In: from datetime import datetime    
    d = datetime.now() #today's datetime
    d

Out:datetime.datetime(2019, 12, 22, 13, 14, 18, 193898)
        
            In:  print(d.weekday()) #day of week - Monday is 0 and Sunday is 6
     print(d.year)
     print(d.month)
     print(d.day)
     print(d.hour)
     print(d.minute)
     print(d.second)

Out: 6
     2019
     12
     22
     13
     14
     18
        

Inoltre, potrebbero essere necessari formati specifici della data/ora. Possiamo utilizzare il seguente elenco per personalizzare diversi formati di data. Questo può essere considerato un modo per convertire la data in stringa.

L’elenco completo dei formati può essere consultato in questa pagina.

articolo python datetime - format date
            In:  print(d.strftime("%A %d/%m/%Y")) # date to string

Out: Sunday 22/12/2019
        

 

Conversione da stringa a data

Di seguito sono riportati esempi che mostrano due stringhe convertite in formato data:

            In:  date_string = '2016-02-01 12:00PM'
     print(datetime.strptime(date_string, '%Y-%m-%d %I:%M%p'))

Out: 2016-02-01 12:00:00

        
            In:  date_string = '02/01/2016'
     d2 = datetime.strptime(date_string, '%m/%d/%Y')
     print(d2)

Out: 2016-02-01 00:00:00
        

 

La differenza nel calcolo di DateTime

L’esempio seguente stampa la differenza in giorni (ad esempio, tra oggi e 1 febbraio 2016):

            In:  from datetime import timedelta     
     
     d = datetime.now()
     date_string = '2/01/2016'
     d2 = datetime.strptime(date_string, '%m/%d/%Y')
     
     print(d - d2)

Out: 1420 days, 13:18:27.386763
        
Possiamo anche stampare solo la differenza tra due date-ora in giorni, settimane o anni, ecc.
            In:  date_diff = (d - d2)/timedelta(days=1)
     print('date_diff = {} days'.format(date_diff))

Out: date_diff = 1420.5544836430902 days

        
            In:  date_diff = (d - d2)/timedelta(weeks=1)
     print('date_diff = {} weeks'.format(date_diff))

Out: date_diff = 202.93635480615575 weeks

        
            In:  date_diff = (d - d2)/timedelta(days=365)
     print('date_diff = {} years'.format(date_diff))

Out: date_diff = 3.8919300921728497 years
        

 

Datetime più/meno un’ora specifica

Facciamo un “viaggio nel tempo” con diversi intervalli di tempo di secondi, minuti, ore, giorni, settimane o anni:

            In:  print(d + timedelta(seconds=1)) # today + one second     
     print(d + timedelta(minutes=1)) # today + one minute     
     print(d + timedelta(hours=1)) # today + one hour  
     print(d + timedelta(days=1)) # today + one day
     print(d + timedelta(weeks=1)) # today + one week
     print(d + timedelta(days=1)*365) # today + one year

Out: 2019-12-22 13:18:28.386763
     2019-12-22 13:19:27.386763
     2019-12-22 14:18:27.386763
     2019-12-23 13:18:27.386763
     2019-12-29 13:18:27.386763
     2020-12-21 13:18:27.386763
        

 

Confronto data/ora

I confronti tra le date sono semplici e sono effettuati con i soliti simboli di confronto:

            In:  print(d < (d2 +(timedelta(days=365*6)))) # d is no more than 6 years (assume each year has 365 days) after d2?
     print(d > (d2 +(timedelta(weeks=52*6)))) # d is more than 6 years (assume each year has 52 weeks) after d2?
     print(d != d2) # d2 is not the same date as d?
     print(d == d2) # d2 is the same date as d?

Out: True
     False
     True
     False
        

 

Impostazioni fusi orari

Possiamo anche confrontare l’ora in diversi fusi orari, come Toronto e Shanghai:

            In:  import pytz
     timezone = pytz.timezone("America/Toronto")
     dtz = timezone.localize(d)
     print(dtz.tzinfo)
     print(dtz)

Out: America/Toronto
     2019-12-22 13:18:27.386763-05:00

        
            In:  shanghai_dt = dtz.astimezone(pytz.timezone("Asia/Shanghai"))
     print(shanghai_dt)

Out: 2019-12-23 02:18:27.386763+08:00

In:  (dtz - shanghai_dt)/timedelta(days=1) # same datetimes

Out: 0.0
        
            In:  (dtz - shanghai_dt)/timedelta(days=1) # same datetimes

Out: 0.0
        
Per visualizzare l’intero elenco dei diversi fusi orari si può utilizzare il codice sottostante:
            In:  for tz in pytz.all_timezones:
        print(tz)

Out: Africa/Abidjan
     Africa/Accra
     Africa/Addis_Ababa
(Stampate solo le prime 3 come esempio)
        

Unix timestamp / calcolo del tempo dell’epoca

I timestamp sono comunemente usati per i file nei sistemi operativi Unix. Spesso vengono visualizzati anche nei set di dati.

Innanzitutto, possiamo ottenere il timestamp Unix corrente:

            In:  from datetime import timezone

     dt_now = datetime.now(timezone.utc)
     print(dt_now)
     print(dt_now.tzinfo)
     print(dt_now.timestamp()) # the unix timestamp.

Out: 2019-12-22 18:21:28.681380+00:00
     UTC
     1577038888.68138
        

Inoltre, possiamo convertire il timestamp Unix nel formato DateTime:

            In:  utc_timestamp = 1377050861.206272
     unix_ts_dt = datetime.fromtimestamp(utc_timestamp, timezone.utc)

     print(unix_ts_dt)
     print(unix_ts_dt.astimezone(pytz.timezone("America/Toronto")))
     print(unix_ts_dt.astimezone(pytz.timezone("Asia/Shanghai")))

Out: 2013-08-21 02:07:41.206272+00:00
     2013-08-20 22:07:41.206272-04:00
     2013-08-21 10:07:41.206272+08:00
        

Come usare il NLP in Python: un pratico esempio passo-passo

scienzadeidati articoli - Come usare NLP in Python

In questo articolo, descriviamo passo dopo passo come applicare il NLP (Natural Language Processing) agli annunci di lavoro.

Se vuoi vedere un esempio pratico usando il pacchetto Natural Language Toolkit (NLTK) con codice Python, questo articolo è per te.

Iniziamo subito.

Sommario

Preparazione: Scraping dei dati

In questo articolo abbiamo bisogno di un dataset iniziale. Per i nostri scopi abbiamo creato un dataset con gli annunci di lavoro per “data scientist” per 8 diverse città tramite un web scraping da Indeed.com. Abbiamo scaricato i dati in file separati per ciascuna città.

Le 8 città incluse in questa analisi sono Boston, Chicago, Los Angeles, Montreal, New York, San Francisco, Toronto e Vancouver . Le variabili sono job_title, company, location e job_description.

Non entriamo nei dettagli di questo processo di web scraping.

Ora siamo pronti per la vera analisi! Da questi dati vogliamo estrarre le informazioni relative agli strumenti popolari, le competenze e l’istruzione minima richiesti dai datori di lavoro .

Step #1: Caricamento e pulizia dei dati

Per prima cosa, carichiamo e unifichiamo i file di dati delle 8 città usando Python.

				
					from collections import Counter
import nltk
import string
from nltk.tokenize import word_tokenize

import math

from plotly import __version__
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
import plotly.graph_objs as go

init_notebook_mode(connected=True)

# load in the data.
df_list = []
cities = ['boston', 'chicago', 'la', 'montreal', 'ny', 'sf', 'toronto', 'vancouver']

for city in cities:
    df_tmp = pd.read_pickle('data_scientist_{}.pkl'.format(city))
    df_tmp['city'] = city
    df_list.append(df_tmp)

df = pd.concat(df_list).reset_index(drop=True)

# make the city names nicer.
msk = df['city'] == 'la'
df.loc[msk, 'city'] = 'los angeles'

msk = df['city'] == 'ny'
df.loc[msk, 'city'] = 'new york'

msk = df['city'] == 'sf'
df.loc[msk, 'city'] = 'san francisco'
				
			

Rimuoviamo le righe/gli annunci di lavoro duplicati con le stesse caratteristiche di job_title, job_description e city.

				
					# If it's the same job description in the same city, for the same job title, we consider it duplicate.
print(df.shape)
df = df.drop_duplicates(subset=['job_description', 'city', 'job_title'])
print(df.shape)
				
			
Ora abbiamo un set di dati con 5 colonne e 2.681 righe.
articoli - NLP python 1

Step #2: Formare gli elenchi di parole chiave

Prima di cercare nelle descrizioni dei lavori, abbiamo bisogno di elenchi di parole chiave che rappresentino gli strumenti / le competenze / i titoli di studio.

Per questa analisi, utilizziamo un semplice approccio per  creare le liste. Gli elenchi si basano sul nostro giudizio e sul contenuto degli annunci di lavoro. È possibile utilizzare approcci più avanzati per attività più complicate di questa.

Per l’elenco delle parole chiave degli strumenti , inizialmente creiamo un elenco basato sulla nostra conoscenza della scienza dei dati. Sappiamo che gli strumenti popolari per i data scientist includono Python, R, Hadoop, Spark e altri. Abbiamo una discreta conoscenza del settore. Quindi questo elenco iniziale è esaustivo ed include i principali strumenti menzionati negli annunci di lavoro.

Quindi esaminiamo annunci di lavoro in modo casuale e aggiungiamo strumenti che non sono ancora nell’elenco. Spesso queste nuove parole chiave ci ricordano di aggiungere anche altri strumenti correlati.

Dopo questo processo, abbiamo un elenco di parole chiave che copre la maggior parte degli strumenti menzionati negli annunci di lavoro.

Successivamente, separiamo le parole chiave in un elenco di parole singole e un elenco di più parole. Abbiamo bisogno di abbinare questi due elenchi con la descrizione del lavoro tramite diverse logiche e metodologie.

Con semplici corrispondenze di stringhe, la parola chiave composta da più parole è spesso unica ed è facile da identificare nella descrizione della posizione lavorativa.

La parola chiave di una sola parola, come “c”, si riferisce al linguaggio di programmazione C. Ma “c” è anche una lettera comune che viene usata in molte parole tra cui “can”, “clustering”. Dobbiamo elaborarli ulteriormente (attraverso la tokenizzazione) in modo che corrispondano solo quando è presente una sola lettera “c” nelle descrizioni dei lavori.

Di seguito sono riportati i nostri elenchi di parole chiave per strumenti codificati in Python.

				
					# got these keywords by looking at some examples and using existing knowledge.
tool_keywords1 = ['python', 'pytorch', 'sql', 'mxnet', 'mlflow', 'einstein', 'theano', 'pyspark', 'solr', 'mahout', 
 'cassandra', 'aws', 'powerpoint', 'spark', 'pig', 'sas', 'java', 'nosql', 'docker', 'salesforce', 'scala', 'r', 'c', 'c++', 'net', 'tableau', 'pandas', 'scikitlearn', 'sklearn', 'matlab', 'scala', 'keras', 'tensorflow', 'clojure', 'caffe', 'scipy', 'numpy', 'matplotlib', 'vba', 'spss', 'linux', 'azure', 'cloud', 'gcp', 'mongodb', 'mysql', 'oracle', 'redshift', 'snowflake', 'kafka', 'javascript', 'qlik', 'jupyter', 'perl', 'bigquery', 'unix', 'react', 'scikit', 'powerbi', 's3', 'ec2', 'lambda', 'ssrs', 'kubernetes', 'hana', 'spacy', 'tf', 'django', 'sagemaker', 'seaborn', 'mllib', 'github', 'git', 'elasticsearch', 'splunk', 'airflow', 'looker', 'rapidminer', 'birt', 'pentaho',  'jquery', 'nodejs', 'd3', 'plotly', 'bokeh', 'xgboost', 'rstudio', 'shiny', 'dash', 'h20', 'h2o', 'hadoop', 'mapreduce',  'hive', 'cognos', 'angular', 'nltk', 'flask', 'node', 'firebase', 'bigtable', 'rust', 'php', 'cntk', 'lightgbm',  'kubeflow', 'rpython', 'unixlinux', 'postgressql', 'postgresql', 'postgres', 'hbase', 'dask', 'ruby', 'julia', 'tensor',
# added r packages doesn't seem to impact the result
'dplyr', 'ggplot2', 'esquisse', 'bioconductor', 'shiny' , 'lubridate','knitr','mlr', 'quanteda', 'dt', 'rcrawler', 'caret', 'rmarkdown', 'leaflet', 'janitor', 'ggvis', 'plotly', 'rcharts', 'rbokeh', 'broom', 'stringr', 'magrittr', 'slidify', 'rvest',  'rmysql', 'rsqlite', 'prophet', 'glmnet', 'text2vec', 'snowballc', 'quantmod', 'rstan', 'swirl', 'datasciencer']


# another set of keywords that are longer than one word.
tool_keywords2 = set(['amazon web services', 'google cloud', 'sql server'])
				
			

Otteniamo elenchi di parole chiave per le abilità seguendo un processo simile a quello degli strumenti.

				
					# hard skills/knowledge required.
skill_keywords1 = set(['statistics', 'cleansing', 'chatbot', 'cleaning', 'blockchain', 'causality', 'correlation', 'bandit', 'anomaly', 'kpi',  'dashboard', 'geospatial', 'ocr', 'econometrics', 'pca', 'gis', 'svm', 'svd', 'tuning', 'hyperparameter', 'hypothesis',  'salesforcecom', 'segmentation', 'biostatistics', 'unsupervised', 'supervised', 'exploratory',  'recommender', 'recommendations', 'research', 'sequencing', 'probability', 'reinforcement', 'graph', 'bioinformatics', 'chi', 'knn', 'outlier', 'etl', 'normalization', 'classification', 'optimizing', 'prediction', 'forecasting',  'clustering', 'cluster', 'optimization', 'visualization', 'nlp', 'c#', 'regression', 'logistic', 'nn', 'cnn', 'glm',  'rnn', 'lstm', 'gbm', 'boosting', 'recurrent', 'convolutional', 'bayesian',  'bayes'])


# another set of keywords that are longer than one word.
skill_keywords2 = set(['random forest', 'natural language processing', 'machine learning', 'decision tree', 'deep learning', 'experimental design',  'time series', 'nearest neighbors', 'neural network', 'support vector machine', 'computer vision', 'machine vision', 'dimensionality reduction', 'text analytics', 'power bi', 'a/b testing', 'ab testing', 'chat bot', 'data mining'])
				
			

Per il titolo di studio, utilizziamo una procedura diversa.

Poiché stiamo cercando minimo il livello di istruzione richiesto, abbiamo bisogno di un valore numerico per classificare il grado di istruzione. Ad esempio, usiamo 1 per rappresentare “bachelor” o “studente”, 2 per rappresentare “master” o “laureato”, e così via.

In questo modo, abbiamo una graduatoria dei titoli in base a numeri da 1 a 4. Più alto è il numero, più alto è il livello di istruzione.

				
					degree_dict = {'bs': 1, 'bachelor': 1, 'undergraduate': 1, 
               'master': 2, 'graduate': 2, 'mba': 2.5, 
               'phd': 3, 'ph.d': 3, 'ba': 1, 'ma': 2,
               'postdoctoral': 4, 'postdoc': 4, 'doctorate': 3}


degree_dict2 = {'advanced degree': 2, 'ms or': 2, 'ms degree': 2, '4 year degree': 1, 'bs/': 1, 'ba/': 1,
                '4-year degree': 1, 'b.s.': 1, 'm.s.': 2, 'm.s': 2, 'b.s': 1, 'phd/': 3, 'ph.d.': 3, 'ms/': 2,
                'm.s/': 2, 'm.s./': 2, 'msc/': 2, 'master/': 2, 'master\'s/': 2, 'bachelor\s/': 1}
degree_keywords2 = set(degree_dict2.keys())
				
			

Step #3: Razionalizzazione delle descrizioni dei lavori utilizzando le tecniche NLP

In questo passaggio, semplifichiamo il testo della descrizione del lavoro. Rendiamo il testo più facile da capire dagli algoritmi; e quindi più efficiente per abbinare il testo con gli elenchi di parole chiave.

La funzione job_description nel nostro set di dati è simile a questa.

				
					df['job_description'].iloc[12]
				
			
articoli - NLP python 2

Tokenizzazione delle descrizioni dei lavori

La tokenizzazione è un processo di analisi delle stringhe di testo in diverse sezioni (token). Questo passaggio è necessario perchè gli algoritmi comprendono meglio il testo tokenizzato. Dobbiamo dividere in modo esplicito la stringa di testo della descrizione del lavoro in diversi token (parole) con delimitatori come lo spazio (” “). Usiamo la funzione word_tokenize per gestire questo compito.
				
					word_tokenize(df['job_description'].iloc[12])
				
			

Dopo questo processo, la stringa di testo della descrizione del lavoro viene partizionata in token (parole), come nella seguente figura. Gli algoritmi possono leggere ed elaborare più facilmente questi token.

Ad esempio, la parola chiave di una sola parola “c” può corrispondere solo con i token (parole) “c”, piuttosto che con altre parole “can” o “clustering”.

articoli - NLP python 3

Parts of Speech (POS) che contrassegnano le descrizioni dei lavori

Le descrizioni dei lavori sono spesso lunghe. Vogliamo mantenere le parole che sono informative per la nostra analisi filtrando le altre. Utilizziamo la codifica POS per raggiungere questo obiettivo.

La codifica POS è un metodo NLP per etichettare se una parola è un sostantivo, aggettivo, verbo, ecc. Wikipedia lo spiega bene:

La codifica POS è il processo di contrassegnare una parola in un testo (corpus) come corrispondente a una parte particolare del discorso, in base sia alla sua definizione che al suo contesto, ovvero la sua relazione con parole adiacenti e correlate in una frase, frase o paragrafo. Una forma semplificata di questo è comunemente insegnata ai bambini in età scolare, nell’identificazione di parole come nomi, verbi, aggettivi, avverbi, ecc.

Grazie a NLTK, possiamo usare questo metodo in Python.

Applicando questa tecnica alle liste di parole chiave, possiamo trovare dei tag relativi alla nostra analisi.

Di seguito codifichiamo con POS l’elenco di parole chiave per gli strumenti.

				
					from nltk import pos_tag
from nltk.stem import PorterStemmer

pos_tag(tool_keywords1)
				
			

Diverse combinazioni di lettere rappresentano i tag. Ad esempio, NN sta per nomi e parole singolari come “python”, JJ sta per aggettivi come “big”. L’elenco completo delle rappresentazioni è disponibile in questo sito.

Come possiamo vedere, il tagger non è perfetto. Ad esempio, “sql” è etichettato come “JJ” — aggettivo. Ma è ancora abbastanza buono per aiutarci a filtrare parole utili.

Usiamo questo elenco di tag di tutte le parole chiave come filtro per le descrizioni dei lavori. Conserviamo solo le parole delle descrizioni dei lavori che hanno gli stessi tag di parole chiave. Ad esempio, manterremmo le parole delle descrizioni dei lavori con i tag “NN” e “JJ”. In questo modo, filtriamo le parole dalle descrizioni del lavoro come “il”, “allora” che non sono informative per la nostra analisi.

In questa fase, abbiamo semplificato le descrizioni dei lavori che vengono tokenizzate e abbreviate.

Ora dobbiamo solo elaborarli un po’ di più.

Step #4: Elaborazione finale delle parole chiave e delle descrizioni dei lavori

In questo passaggio, elaboriamo ulteriormente sia gli elenchi di parole chiave che le descrizioni dei lavori.

Stemming delle parole

Lo stemming delle parole è il processo di riduzione delle parole flesse (o talvolta derivate) alla loro forma base o radice della parola, generalmente una forma di parola scritta.

Il processo di stemming consente agli algoritmi di identificare le parole con la stessa radice nonostante il loro aspetto diverso. In questo modo, possiamo abbinare le parole purché abbiano la stessa radice. Ad esempio, le parole “modelli”, “modellare” hanno entrambe la stessa radice “modello”.

Disponiamo sia degli elenchi di parole chiave che delle descrizioni dei lavori semplificate.

Formattazione minuscola delle parole

Infine, standardizziamo tutte le parole mettendole in minuscolo. Dobbiamo formattare in minuscolo le descrizioni dei lavori poiché gli elenchi di parole chiave sono costruiti in minuscolo.

Come accennato nelle sezioni precedenti, il codice Python utilizzato nelle procedure precedenti è riportato di seguito.

				
					from nltk import pos_tag
from nltk.stem import PorterStemmer

ps = PorterStemmer()


# process the job description.
def prepare_job_desc(desc):
    # tokenize description.
    tokens = word_tokenize(desc)
        
    # Parts of speech (POS) tag tokens.
    token_tag = pos_tag(tokens)
    
    # Only include some of the POS tags.
    include_tags = ['VBN', 'VBD', 'JJ', 'JJS', 'JJR', 'CD', 'NN', 'NNS', 'NNP', 'NNPS']
    filtered_tokens = [tok for tok, tag in token_tag if tag in include_tags]
    
    # stem words.
    stemmed_tokens = [ps.stem(tok).lower() for tok in filtered_tokens]
    return set(stemmed_tokens)

df['job_description_word_set'] = df['job_description'].map(prepare_job_desc)

# process the keywords
tool_keywords1_set = set([ps.stem(tok) for tok in tool_keywords1]) # stem the keywords (since the job description is also stemmed.)
tool_keywords1_dict = {ps.stem(tok):tok for tok in tool_keywords1} # use this dictionary to revert the stemmed words back to the original.

skill_keywords1_set = set([ps.stem(tok) for tok in skill_keywords1])
skill_keywords1_dict = {ps.stem(tok):tok for tok in skill_keywords1}

degree_keywords1_set = set([ps.stem(tok) for tok in degree_dict.keys()])
degree_keywords1_dict = {ps.stem(tok):tok for tok in degree_dict.keys()}
				
			

Ora rimangono solo le parole (token) nelle descrizioni dei lavori che sono legate alla nostra analisi.

Di seguito è riportato un esempio finale delle descrizione dei lavori.

				
					df['job_description_word_set'].iloc[10]
				
			
articoli - NLP python 5
Finalmente siamo pronti per verificare le corrispondenze con le parole chiave!

Step #5: Abbinare le parole chiave e le descrizioni del lavoro

Per vedere se una descrizione di lavoro menziona parole chiave specifiche, abbiniamo gli elenchi di parole chiave e le descrizioni di lavoro semplificate finali.

Strumenti/Abilità

Come ricorderete, abbiamo creato due tipi di elenchi di parole chiave: l’elenco di parole singole e l’elenco di più parole. Per le parole chiave di una sola parola, abbiniamo ciascuna parola chiave alla descrizione del lavoro mediante la funzione di intersezione impostata. Per le parole chiave composte da più parole, controlliamo se sono sottostringhe delle descrizioni dei lavori.

Formazione scolastica

Per il livello di istruzione, utilizziamo lo stesso metodo degli strumenti/abilità per abbinare le parole chiave. Tuttavia, teniamo traccia solo del livello minimo.

Ad esempio, quando le parole chiave “bachelor” e “master” esistono entrambe in una descrizione del lavoro, la laurea è l’istruzione minima richiesta per questo lavoro.

Il codice Python con maggiori dettagli è di seguito.

				
					tool_list = []
skill_list = []
degree_list = []

msk = df['city'] != '' # just in case you want to filter the data.

num_postings = len(df[msk].index)
for i in range(num_postings):
    job_desc = df[msk].iloc[i]['job_description'].lower()
    job_desc_set = df[msk].iloc[i]['job_description_word_set']
    
    # check if the keywords are in the job description. Look for exact match by token.
    tool_words = tool_keywords1_set.intersection(job_desc_set)
    skill_words = skill_keywords1_set.intersection(job_desc_set)
    degree_words = degree_keywords1_set.intersection(job_desc_set)
    
    # check if longer keywords (more than one word) are in the job description. Match by substring.
    j = 0
    for tool_keyword2 in tool_keywords2:
        # tool keywords.
        if tool_keyword2 in job_desc:
            tool_list.append(tool_keyword2)
            j += 1
    
    k = 0
    for skill_keyword2 in skill_keywords2:
        # skill keywords.
        if skill_keyword2 in job_desc:
            skill_list.append(skill_keyword2)
            k += 1
    
    # search for the minimum education.
    min_education_level = 999
    for degree_word in degree_words:
        level = degree_dict[degree_keywords1_dict[degree_word]]
        min_education_level = min(min_education_level, level)
    
    for degree_keyword2 in degree_keywords2:
        # longer keywords. Match by substring.
        if degree_keyword2 in job_desc:
            level = degree_dict2[degree_keyword2]
            min_education_level = min(min_education_level, level)
    
    # label the job descriptions without any tool keywords.
    if len(tool_words) == 0 and j == 0:
        tool_list.append('nothing specified')
    
    # label the job descriptions without any skill keywords.
    if len(skill_words) == 0 and k == 0:
        skill_list.append('nothing specified')
    
    # If none of the keywords were found, but the word degree is present, then assume it's a bachelors level.
    if min_education_level > 500:
        if 'degree' in job_desc:
            min_education_level = 1
    
    tool_list += list(tool_words)
    skill_list += list(skill_words)
    degree_list.append(min_education_level)
				
			

Step #6: Visualizzazione dei risultati

Riassumiamo i risultati con grafici a barre.

Per ogni particolare parola chiave di strumenti/abilità/livelli di istruzione, contiamo il numero di descrizioni di lavoro che corrispondono ad esse. Calcoliamo anche la loro percentuale tra tutte le descrizioni dei lavori.

Per gli elenchi di strumenti e abilità, presentiamo solo i primi 50 più popolari. Per il livello di istruzione, li riassumiamo in base al livello minimo richiesto.

Il codice Python dettagliato è di seguito.

I strumenti più richiesti

				
					# create the list of tools.
df_tool = pd.DataFrame(data={'cnt': tool_list})
df_tool = df_tool.replace(tool_keywords1_dict)

# group some of the categories together.
msk = df_tool['cnt'] == 'h20'
df_tool.loc[msk, 'cnt'] = 'h2o'

msk = df_tool['cnt'] == 'aws'
df_tool.loc[msk, 'cnt'] = 'amazon web services'

msk = df_tool['cnt'] == 'gcp'
df_tool.loc[msk, 'cnt'] = 'google cloud'

msk = df_tool['cnt'] == 'github'
df_tool.loc[msk, 'cnt'] = 'git'

msk = df_tool['cnt'] == 'postgressql'
df_tool.loc[msk, 'cnt'] = 'postgres'

msk = df_tool['cnt'] == 'tensor'
df_tool.loc[msk, 'cnt'] = 'tensorflow'

df_tool_top50 = df_tool['cnt'].value_counts().reset_index().rename(columns={'index': 'tool'}).iloc[:50]
				
			
				
					# visualize the tools.
layout = dict(
    title='Tools For Data Scientists',
    yaxis=dict(
        title='% of job postings',
        tickformat=',.0%',
    )
)

fig = go.Figure(layout=layout)
fig.add_trace(go.Bar(
    x=df_tool_top50['tool'],
    y=df_tool_top50['cnt']/num_postings
))

iplot(fig)
				
			
articoli - NLP python 6

Le competenze più richieste

				
					# create the list of skills/knowledge.
df_skills = pd.DataFrame(data={'cnt': skill_list})
df_skills = df_skills.replace(skill_keywords1_dict)

# group some of the categories together.
msk = df_skills['cnt'] == 'nlp'
df_skills.loc[msk, 'cnt'] = 'natural language processing'

msk = df_skills['cnt'] == 'convolutional'
df_skills.loc[msk, 'cnt'] = 'convolutional neural network'

msk = df_skills['cnt'] == 'cnn'
df_skills.loc[msk, 'cnt'] = 'convolutional neural network'

msk = df_skills['cnt'] == 'recurrent'
df_skills.loc[msk, 'cnt'] = 'recurrent neural network'

msk = df_skills['cnt'] == 'rnn'
df_skills.loc[msk, 'cnt'] = 'recurrent neural network'

msk = df_skills['cnt'] == 'knn'
df_skills.loc[msk, 'cnt'] = 'nearest neighbors'

msk = df_skills['cnt'] == 'svm'
df_skills.loc[msk, 'cnt'] = 'support vector machine'

msk = df_skills['cnt'] == 'machine vision'
df_skills.loc[msk, 'cnt'] = 'computer vision'

msk = df_skills['cnt'] == 'ab testing'
df_skills.loc[msk, 'cnt'] = 'a/b testing'

df_skills_top50 = df_skills['cnt'].value_counts().reset_index().rename(columns={'index': 'skill'}).iloc[:50]
				
			
				
					# visualize the skills.
layout = dict(
    title='Skills For Data Scientists',
    yaxis=dict(
        title='% of job postings',
        tickformat=',.0%',
    )
)

fig = go.Figure(layout=layout)
fig.add_trace(go.Bar(
    x=df_skills_top50['skill'],
    y=df_skills_top50['cnt']/num_postings
))

iplot(fig)
				
			
articoli - NLP python 7

Istruzione minima richiesta

				
					# create the list of degree.
df_degrees = pd.DataFrame(data={'cnt': degree_list})
df_degrees['degree_type'] = ''


msk = df_degrees['cnt'] == 1
df_degrees.loc[msk, 'degree_type'] = 'bachelors'

msk = df_degrees['cnt'] == 2
df_degrees.loc[msk, 'degree_type'] = 'masters'

msk = df_degrees['cnt'] == 3
df_degrees.loc[msk, 'degree_type'] = 'phd'

msk = df_degrees['cnt'] == 4
df_degrees.loc[msk, 'degree_type'] = 'postdoc'

msk = df_degrees['cnt'] == 2.5
df_degrees.loc[msk, 'degree_type'] = 'mba'

msk = df_degrees['cnt'] > 500
df_degrees.loc[msk, 'degree_type'] = 'not specified'


df_degree_cnt = df_degrees['degree_type'].value_counts().reset_index().rename(columns={'index': 'degree'}).iloc[:50]

				
			
				
					# visualize the degrees.
layout = dict(
    title='Minimum Education For Data Scientists',
    yaxis=dict(
        title='% of job postings',
        tickformat=',.0%',
    )
)

fig = go.Figure(layout=layout)
fig.add_trace(go.Bar(
    x=df_degree_cnt['degree'],
    y=df_degree_cnt['degree_type']/num_postings
))

iplot(fig)
				
			

Ce l’abbiamo fatta !

Spero che questo articolo sia stato utile. Lascia un commento per farmi sapere i tuoi dubbi e suggerimenti.