Este es el material de una clase que doy en Digital House en el curso de ciencia de datos.

¿Qué es un grafo?

  • Red — grafo, graph
    • Nodos — vértices, nodes
    • Conexiones — aristas, bordes, edges

¿Para qué sirven?

Redes de transporte

Redes de interacción

Redes de comunicación

Es una estructura de datos útil para representar:

  • redes sociales
  • máquinas de estado
  • moléculas
  • mapas conceptuales
  • estructura del lenguaje
  • redes de transporte
  • y mucho más...

NetworkX

https://networkx.github.io

NetworkX es un paquete de Python para la creación, manipulación, y el estudio de la estructura, dinámica, y las funciones de las redes complejas.

!pip install --upgrade networkx
%matplotlib inline

import networkx as nx

G = nx.Graph()
G.add_node('Buenos Aires')
G.add_node('Córdoba')
G.add_node('Mendoza')

nx.draw(G)
G.add_edge('Buenos Aires', 'Córdoba', distancia=647)
G.add_edge('Buenos Aires', 'Mendoza', distancia=948)
G.add_edge('Córdoba',      'Mendoza', distancia=682)

nx.draw(G)
G.add_edge('Tucumán','Salta', distancia=227)

nx.draw(G)
  • Subgrafo: subconjunto de nodos y conexiones.
  • Componente conectada: grupo de nodos conectados.
G.nodes
NodeView(('Buenos Aires', 'Córdoba', 'Mendoza', 'Tucumán', 'Salta'))
G.edges
EdgeView([('Buenos Aires', 'Córdoba'), ('Buenos Aires', 'Mendoza'), ('Córdoba', 'Mendoza'), ('Tucumán', 'Salta')])
G.degree['Mendoza']
2

Graficando

pos=nx.planar_layout(G)

# dibujar etiquetas
nx.draw_networkx_labels(G, pos=pos)

# dibujar nodos
nx.draw_networkx_nodes(G, pos=pos)

# dibujar conexiones
nx.draw_networkx_edges(G, pos=pos);

Graficar grafos no es una tarea sencilla y NetworkX lo deja en mano de otras aplicaciones, como Gephi.

Tipos de grafos

Dirigido

nx.DiGraph

Multigrafo

nx.MultiGraph, nx.MultiDiGraph

Algoritmos comunes

Grafo de ejemplo: Familias florentinas

Familias que se disputaron el control político de la ciudad de Florencia alrededor de 1430. Dos facciones fueron dominantes en la disputa: Medicis y Strozzis.

Dataset de uniones maritales y de negocio entre familias.

Fuente: Padgett, J. F., & Ansell, C. K. (1993). Robust Action and the Rise of the Medici, 1400-1434. American Journal of Sociology, 98(6), 1259-1319.

G = nx.florentine_families_graph()

nx.draw_networkx(G)
G.nodes
NodeView(('Acciaiuoli', 'Medici', 'Castellani', 'Peruzzi', 'Strozzi', 'Barbadori', 'Ridolfi', 'Tornabuoni', 'Albizzi', 'Salviati', 'Pazzi', 'Bischeri', 'Guadagni', 'Ginori', 'Lamberteschi'))
for sub_graph in nx.connected_components(G):
    print(sub_graph)
{'Strozzi', 'Bischeri', 'Acciaiuoli', 'Salviati', 'Pazzi', 'Castellani', 'Ridolfi', 'Albizzi', 'Peruzzi', 'Medici', 'Ginori', 'Lamberteschi', 'Barbadori', 'Tornabuoni', 'Guadagni'}

El camino más corto

https://en.wikipedia.org/wiki/Shortest_path_problem

  • Camino: secuencia de nodos conectados.

Aplicaciones

  • Google Maps
  • LinkedIn
nx.shortest_path(G, source='Medici', target='Strozzi', weight=None)
['Medici', 'Ridolfi', 'Strozzi']

Árbol recubridor mínimo

https://en.wikipedia.org/wiki/Minimum_spanning_tree

Aplicaciones

  • Tendido de redes
mst = nx.minimum_spanning_tree(G, weight=None)

nx.draw_networkx(mst)
rank = nx.pagerank(G, weight=None)

sorted(rank.items(), key=lambda item: item[1], reverse=True)[:5]
[('Medici', 0.14581844065218275),
 ('Guadagni', 0.09839859717156552),
 ('Strozzi', 0.08809849082725613),
 ('Albizzi', 0.079121502380729),
 ('Tornabuoni', 0.07127928374676082)]

Ejemplo de uso en aprendizaje automático: Red de influencia de inversores.

rank = nx.betweenness_centrality(G, weight=None)

sorted(rank.items(), key=lambda item: item[1], reverse=True)[:5]
[('Medici', 0.521978021978022),
 ('Guadagni', 0.2545787545787546),
 ('Albizzi', 0.21245421245421245),
 ('Salviati', 0.14285714285714288),
 ('Ridolfi', 0.11355311355311355)]
nx.algorithms.community.modularity_max.greedy_modularity_communities(G)
[frozenset({'Acciaiuoli',
            'Medici',
            'Pazzi',
            'Ridolfi',
            'Salviati',
            'Tornabuoni'}),
 frozenset({'Barbadori', 'Bischeri', 'Castellani', 'Peruzzi', 'Strozzi'}),
 frozenset({'Albizzi', 'Ginori', 'Guadagni', 'Lamberteschi'})]

Índice Adamic/Adar

https://en.wikipedia.org/wiki/Adamic/Adar_index

Aplicaciones

  • Recomendación de contactos
  • Recomendación de productos
predicciones = nx.adamic_adar_index(G, ebunch=[('Medici', 'Strozzi'), ('Medici', 'Pazzi')])

for predicción in predicciones:
    print(predicción)
('Medici', 'Strozzi', 0.9102392266268373)
('Medici', 'Pazzi', 1.4426950408889634)

Pandas

import pandas as pd

URL = 'http://cdn.buenosaires.gob.ar/datosabiertos/datasets/bicicletas-publicas/recorridos-realizados-2019.csv'
ARCHIVO = 'muestreo_recorridos.csv.gz'

df = pd.read_csv(ARCHIVO, low_memory=False)
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2850451 entries, 0 to 2850450
Data columns (total 19 columns):
id_usuario                    object
edad_usuario                  float64
genero_usuario                object
fecha_origen_recorrido        object
id_estacion_origen            object
nombre_estacion_origen        object
direccion_estacion_origen     object
capacidad_estacion_origen     float64
lat_estacion_origen           float64
long_estacion_origen          float64
duracion_recorrido            object
fecha_destino_recorrido       object
id_estacion_destino           object
nombre_estacion_destino       object
direccion_estacion_destino    object
capacidad_estacion_destino    float64
lat_estacion_destino          float64
long_estacion_destino         float64
servicio                      object
dtypes: float64(7), object(12)
memory usage: 413.2+ MB
df.head()
id_usuario edad_usuario genero_usuario fecha_origen_recorrido id_estacion_origen nombre_estacion_origen direccion_estacion_origen capacidad_estacion_origen lat_estacion_origen long_estacion_origen duracion_recorrido fecha_destino_recorrido id_estacion_destino nombre_estacion_destino direccion_estacion_destino capacidad_estacion_destino lat_estacion_destino long_estacion_destino servicio
0 137063_0 26.0 FEMENINO 2019-01-07 17:06:49 156.0 Plaza Alemania Plaza Alemania: Del Libertador Av y Cavia NaN -34.577849 -58.407966 0 days 00:24:48.000000000 2019-01-07 17:31:37 170.0 San Luis y Ecuador San Luis 2862 entre Ecuador y Boulogne Sur Mer NaN -34.599733 -58.405966 antiguo
1 79103 NaN NaN 2019-06-10 11:51:12 29.0 Parque Centenario Patricias Argentinas Av. y Estivao 30.0 -34.607941 -58.433557 0 days 00:24:57.000000000 2019-06-10 12:16:09 87.0 Guayaquil Guayaquil y Doblas 16.0 -34.619845 -58.431494 nuevo
2 387321 NaN NaN 2019-07-30 15:20:38 289.0 BARRANCAS DE BELGRANO Sucre, Antonio Jose De, Mcal. y Vertiz Virrey Av. 24.0 -34.559793 -58.448432 0 days 00:51:39.000000000 2019-07-30 16:12:17 175.0 Constitución Garay, Juan De Av. 1050 36.0 -34.626741 -58.380935 nuevo
3 113196 NaN NaN 2019-04-28 00:59:32 56.0 Plaza Palermo Viejo Costa Rica y Armenia 16.0 -34.588567 -58.425999 0 days 00:21:24.000000000 2019-04-28 01:20:56 69.0 Ecuador Ecuador 1226 16.0 -34.596101 -58.404609 nuevo
4 248012 NaN NaN 2019-06-12 15:04:26 153.0 JUAN MANUEL DE BLANES Blanes, Juan Manuel 383 16.0 -34.630777 -58.362070 0 days 00:13:16.000000000 2019-06-12 15:17:42 126.0 MINISTERIO DE JUSTICIA Y SEGURIDAD Regimiento De Patricios Av. y Araoz De Lamadri... 16.0 -34.640267 -58.369224 nuevo

El DataFrame debe contener al menos dos columnas con nombres de nodos (origen y destino) y cero o más columnas con atributos de las conexiones.

Cada fila se procesa como una conexión.

df['minutos_viaje'] = pd.to_timedelta(df.duracion_recorrido, unit='minute', errors='coerce')
pre_grafo = df.groupby(['nombre_estacion_origen','nombre_estacion_destino']) \
    .minutos_viaje \
    .mean(numeric_only=False) \
    .to_frame() \
    .reset_index()

pre_grafo.head()
nombre_estacion_origen nombre_estacion_destino minutos_viaje
0 11 de septiembre 11 de septiembre 00:59:58.833333
1 11 de septiembre Arribeños 00:08:51
2 11 de septiembre BARRANCAS DE BELGRANO 00:14:15
3 11 de septiembre Balbín 00:10:22
4 11 de septiembre Catedral 00:54:11
estaciones = nx.convert_matrix.from_pandas_edgelist(
    pre_grafo,
    source='nombre_estacion_origen',
    target='nombre_estacion_destino',
    edge_attr='minutos_viaje',
    create_using=nx.MultiDiGraph
)
nx.draw(estaciones)

😱 😱 😱

Ideas