TDC 2017
Navegando por grafos com Python
Bernardo Fontes
São Paulo/SP
22 de Julho de 2017
## 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**