TDC 2017

Navegando por grafos com Python


Bernardo Fontes


São Paulo/SP

22 de Julho de 2017

Só para dizer um oi!

twitter.com/bbfontes

github.com/berinhard

pessoas.cc

bernardoxhc@gmail.com

berinhard.github.io/talks/

## WARNING! ### O Teoria de Grafos é uma **área inteira** em Ciência da Computação. ### Iremos algumas partes dela nessa palestra para avaliarmos a **utilidade** do tema. ### Encorajo muito a buscarem mais informações e trocarmos ideia depois da palestra.
## Roteiro - Exemplo real - Nosso exemplo - Persistindo no banco - Analisando as métricas

Network of Thrones

por Andrew Beveridge e Jie Shan

## Análise dos dados - Grafo em que os **personagens são os nós** - Nomes de **personagens** aparecendo a uma **distância de 15 palavras** gera uma **conexão** entre eles

Resultado

## Métricas de centralidade - **Degree centrality**: quantas conexões possui? - **Eigenvector centrality**: quantas conexões com personagens **importantes**? - **Betweenness Centrality**: são pontos de conexão de diferente partes do grafo? - **SPOILER**: as métricas indicaram que **Tyrion é o protagonista**.
## Quando usar grafos? - Quando o *domínio do problema* e o *entendimento dos dados* é muito mais atrelado a **como as entidades se relacionam** do que com suas mudanças de estado - Grafos visualizam as relações **diretamente**, ou seja, são **entidades** por si só - **Nosso grafo**: pessoas que se conhecem
## Persistência: Neo4j - BD Orientado a Grafos escrito em **Java** - **Cypher** query language == SQL do Neo4j - **Bolt** é o protocolo de comunicação utilizado - Módulo **py2neo** para comunicação com o banco
## Criando a conexão ```python from py2neo import Graph as Py2NeoGraph graph_kwargs = { 'host': 'localhost', 'http_port': 7475, 'bolt_port': 7688, } graph = Py2NeoGraph(**graph_kwargs) ```
## Criando o grafo ```python from py2neo import Node, Relationship mari = Node('Person', name='Mari', gender='F') # propriedades do nó bernardo = Node('Person', name='Bernardo', gender='M') fabio = Node('Person', name='Fábio', gender='M') rel1 = Relationship(mari, 'KNOWS', bernardo) # KNOWS == tipo transaction = graph.begin() transaction.create(mari) transaction.create(bernardo) transaction.create(fabio) transaction.create(rel1) transaction.commit() ```

Grafo (17 nós e 26 arestas)

Brincando com o grafo

Recuperando o grafo via Cypher

            
              MATCH
                p=(:Person)-[r:KNOWS]->(:Person)
              RETURN p;
            
          

Grafo de pessoas que conhecem mulheres

            
              MATCH
                p=(:Person)-[:KNOWS]->(target:Person)
              WHERE
                target.gender = 'F'
              RETURN p;
            
          

Grafo somente das mulheres

            
              MATCH
                p=(source:Person)-[r:KNOWS]->(target:Person)
              WHERE
                target.gender='F' AND source.gender='F'
              RETURN p;
            
          

Pessoas que se conhecem mutuamente

            
              MATCH
                p=(source:Person)-[r:KNOWS]->()-[:KNOWS]->(target:Person)
              WHERE
                target.name = source.name
              RETURN p;
            
          
## Analisando o grafo - `networkx`: lib python para **processamento de grafo** - Ler o grafo do Neo4J - Popular um grafo do networkx - Tirar as métricas
## Lendo o grafo do Neo4J ```python from pprint import pprint query = 'MATCH p=(:Person)-[r:KNOWS]->(:Person) RETURN p;' data = graph.data(query) pprint(data) [{'p': (mari)-[:KNOWS]->(bernardo)}, {'p': (claudia)-[:KNOWS]->(`fábio`)}, {'p': (`antônio`)-[:KNOWS]->(`fábio`)}, {'p': (mari)-[:KNOWS]->(`fábio`)}, {'p': (`joão`)-[:KNOWS]->(`fábio`)}, ......] ```
## O grafo no networkx ```python nx_graph = nx.DiGraph() # grafo direcional relationships = [d['p'] for d in data] for relationship in relationships: source, target = relationship.start_node(), relationship.end_node() nx_graph.add_node(source['name'], gender=source['gender']) nx_graph.add_node(target['name'], gender=target['gender']) nx_graph.add_edge(source['name'], target['name']) print('Nodes: {}'.format(nx_graph.number_of_nodes())) print('Edges: {}'.format(nx_graph.number_of_edges())) Nodes: 17 Edges: 26 ```

Que algoritmo usar?

## In-Degree centrality: mais famosos ```python in_degree_centrality = nx.in_degree_centrality(nx_graph) sorted(in_degree_centrality.items(), key=lambda x:x[1], reverse=True) [('Fernanda', 0.375), ('Fábio', 0.25), ('José', 0.125), ('Marcia', 0.125), ('Tereza', 0.125), ('Regina', 0.125), ('Bernardo', 0.0625), ('Ana', 0.0625), ('Orlando', 0.0625), ('Jonas', 0.0625), ('Luís', 0.0625), ('Mari', 0.0625), ('Renata', 0.0625), ('Joana', 0.0625), ('Antônio', 0.0), ('João', 0.0), ('Claudia', 0.0)] ```
## Out-Degree centrality: mais conhecedor ```python out_degree_centrality = nx.out_degree_centrality(nx_graph) sorted(out_degree_centrality.items(), key=lambda x:x[1], reverse=True) [('Marcia', 0.375), ('Fábio', 0.25), ('Claudia', 0.25), ('Mari', 0.1875), ('Ana', 0.125), ('Joana', 0.125), ('Bernardo', 0.0625), ('José', 0.0625), ('Antônio', 0.0625), ('João', 0.0625), ('Regina', 0.0625), ('Fernanda', 0.0), ('Orlando', 0.0), ('Jonas', 0.0), ('Luís', 0.0), ('Tereza', 0.0), ('Renata', 0.0)] ```
## Eigenvector centrality: gente influente ```python eigenvector_centrality = nx.eigenvector_centrality(nx_graph) sorted(eigenvector_centrality.items(), key=lambda x:x[1], reverse=True) [('Fernanda', 0.8090398349558906), ('Tereza', 0.26967994498529685), ('Regina', 0.26967994498529685), ('Bernardo', 0.13483997249264842), ('Ana', 0.13483997249264842), ('José', 0.13483997249264842), ('Orlando', 0.13483997249264842), ('Jonas', 0.13483997249264842), ('Marcia', 0.13483997249264842), ('Fábio', 0.13483997249264842), ('Renata', 0.13483997249264842), ('Mari', 0.13483997249264842), ('Luís', 0.13483997249264842), ('Joana', 0.13483997249264842), ('Antônio', 0.0), ('João', 0.0), ('Claudia', 0.0)] ```
## Betweenness centrality: proxies entre grupos ```python betweenness_centrality = nx.betweenness_centrality(nx_graph) sorted(betweenness_centrality.items(), key=lambda x:x[1], reverse=True) [('Fábio', 0.08333333333333333), ('Marcia', 0.075), ('Mari', 0.05), ('Ana', 0.01875), ('Joana', 0.0125), ('Regina', 0.0020833333333333333), ('Bernardo', 0.0), ('José', 0.0), ('Fernanda', 0.0), ('Antônio', 0.0), ('Orlando', 0.0), ('Jonas', 0.0), ('Luís', 0.0), ('Tereza', 0.0), ('João', 0.0), ('Renata', 0.0), ('Claudia', 0.0)] ```
## Diversidade ```python # Assortatividade é a tendência de um nó # de se conectar com outros nós de grau parecido nx.attribute_assortativity_coefficient(nx_graph, 'gender') 0.15827338129496404 ```
## Conclusões - NoSQL **não** é uma estratégia para todos os problemas - A persistência com o py2neo é **chata** - Controle suas **transações no banco manualmente** - O módulo do **networkx** já tem 99% do que você precisa - Saiba quais **perguntas** fazer e quais **métricas** as respondem - Outras possíveis análises poderiam ser **detecção de clusters** ou **identificação de padrões**

Obrigado!


Bernardo Fontes


twitter.com/bbfontes

github.com/berinhard

pessoas.cc

bernardoxhc@gmail.com