# Лабораторная работа 4. «Визуализация связей в виде графов»

[назад](README.md)

При выполнении данной лабораторной работы производится визуализация связей социальных сетей в виде графа.

## 1. Запрос перечня друзей пользователя

Для выполнения работы импортируем необходимый пакет.

```python
 import vk_api
```

Как и при выполнении первой лабораторной работы создаем подключение, а далее находим друзей `user1` с помощью вызова метода `friends.get`.

```python
friends_user1 = tools.get_all('friends.get', 100, {'user_id': user1})
```

На выходе получим перечень `id` друзей пользователя. API VK позволяет дополнительно сразу запрашивать параметры пользователя такие как:

```python
nickname, domain, sex, bdate, city, country, timezone,
photo_50, photo_100, photo_200_orig, has_mobile,
contacts, education, online, relation, last_seen,
status, can_write_private_message, can_see_all_posts,
can_post, universities
```

Необходимые параметры указываются в параметре `fields`. При этом для выполнения текущей задачи имеет ценность лишь список друзей. Поэтому дополнительный параметры указывать не будем.

Итоговый код будет выглядеть следующим образом.

```python
import vk_api
import time
import json
import pickle
user1 = 249771326

def auth_handler():
  """ При двухфакторной аутентификации вызывается эта
функция. """
  # Код двухфакторной аутентификации
  key = input("Enter authentication code: ")
  # Если: True ‐ сохранить, False ‐ не сохранять.
  remember_device = True
  return key, remember_device

def stop_f(items):
  print (items)

def get_groups_users(friends_list, tools):
  friends_out = {}
  for friend in friends_list:
    try:
        friends_out[friend] = tools.get_all('friends.get', 100, {'user_id': friend})
    except Exception:
        friends_out[friend] = []
    time.sleep(1)
  return friends_out

def main():
    login, password = '<ВАШ ЛОГИН>', '<ВАШ ПАРОЛЬ>'
    vk_session = vk_api.VkApi(
            login, 
            password,
            # функция для обработки двухфакторной аутентификации
            auth_handler=auth_handler  
)
  try:
      vk_session.auth()
  except vk_api.AuthError as error_msg:
      print(error_msg)
      
  tools = vk_api.VkTools(vk_session)
  friend_list=[]
  friend_list.append(user1)
  friends_out = get_groups_users(friend_list, tools)
  print(friends_out)

if __name__ == '__main__':
    main()
```

В результате исполнения этого данного получится JSON-response со списком друзей пользователя, `id` которого, был указан в переменной `user1`.

## 2. Формирование графа друзей пользователя

Установим модуль networkX (необходима версия 1.9).

```python
 pip install networkx==1.9
```

Далее реализуем функции для визуализации графа.

```python
import networkx as nx

def make_graph(friends_out, friends_friends):
  graph = nx.Graph()
  graph.add_node(user1, size = friends_out[user1]['count'])
  for i in friends_out[user1]['items']:
    try:
      graph.add_node(i, size=friends_friends[i]['count'])
      intersection = set(friends_out[user1]['items']).intersection(
        set(friends_friends[i]['items']))
      graph.add_edge(user1, i, weight=len(intersection))
    except Exception:
      print("err")
  return graph
```

Данная функция создает граф, вершинами которого являются узлы, содержащие `id` друзей, а также связывает между собой пользователя и его друга. Так как необходимо связать не только пользователя и друзей, а ещё и друзей между собой, то модифицируем код следующим образом.

```python
def make_graph(friends_out, friends_friends):
  graph = nx.Graph()
  graph.add_node(user1, size = friends_out[user1]['count'])

  for i in friends_out[user1]['items']:
    try:
      graph.add_node(i, size = friends_friends[i]['count'])
      intersection = set(friends_out[user1]['items']).intersection(
        set(friends_friends[i]['items']))
      graph.add_edge(user1, i, weight=len(intersection))
    except Exception:
      print("err")

  for i in range(len(friends_out[user1]['items'])):
    id1 = friends_out[user1]['items'][i]
    for k in range(i+1, len(friends_out[user1]['items'])):
      id2 = friends_out[user1]['items'][k]
      try:
        intersection = set(friends_friends[id1]['items']).intersection(
          set(friends_friends[id2]['items']))
        if len(intersection) > 0:
          graph.add_edge(id1, id2, weight=len(intersection))
      except Exception:
        print("err friend")
  return graph
```

Здесь дополнительно добавляется переменная `friends_friends`, которая содержит в себе списки друзей друзей пользователя.

Данная переменная появляется в результате вызова метода `get_groups_users` куда на вход подаем список `id` друзей пользователя.

```python
friend_friends = get_groups_users(friends_out[user1]['items'], tools)
```

Количество запросов ограничено, поэтому с целью экономии предлагается использовать модуль `pickle`. Данный модуль реализует мощный алгоритм сериализации и десериализации объектов Python. "Pickling" – процесс преобразования объекта Python в поток байтов, а "unpickling" – обратная операция, в результате которой поток байтов преобразуется обратно в Python-объект. Поток байтов легко можно записать в файл, поэтому модуль `pickle` широко применяется для сохранения и загрузки сложных объектов в Python.

Пример его использования выглядит следующим образом.

```python
with open('friends_friends.pkl', 'wb') as output:
  pickle.dump(friend_friends, output, pickle.HIGHEST_PROTOCOL)
```

Этот пример для сохранения объекта. Следующий пример – для загрузки объекта.

```python
with open('friends_friends.pkl', 'rb') as input:
  friends_friends = pickle.load(input)
```

Вызовем функцию `make_graph` в функции `main`.

```python
 g = make_graph(friends_out, friend_friends)
```

После формирования связей так же рекомендуется сохранить полученный граф при помощи Pickle.

## 3. Визуализация графа друзей пользователя

Сформированный граф необходимо визуализировать. Для этого будем использовать модуль `matplotlib` (необходимая версия – 2.2.4).

Реализуем функцию визуализации.

```python
def plot_graph(graph, adjust_nodesize):
  #pos = nx.drawing.layout.circular_layout(graph)
  pos=nx.spring_layout(graph, k=0.1)
  # нормализуем размер вершины для визуализации.
  # Оптимальное значение параметра
  #vadjust_nodesize ‐ от 300 до 500
  nodesize = [graph.node[i]['size']/adjust_nodesize for i in graph.nodes()]
  #нормализуем толщину ребра графа. Здесь хорошо подходит
  #нормализация по Standard Score
  edge_mean = numpy.mean([graph.edge[i[0]][i[1]]['weight'] for i in graph.edges()])
  edge_std_dev = numpy.std([graph.edge[i[0]][i[1]]['weight'] for i in graph.edges()])
  edgewidth = [ ((graph.edge[i[0]][i[1]]['weight'] ‐ edge_mean)/edge_std_dev/2) for i in graph.edges()]

  #создаем граф для визуализации
  nx.draw_networkx_nodes(
    graph,
    pos,
    node_size=nodesize, 
    node_color='y', 
    alpha=0.9
  )
  nx.draw_networkx_edges(
    graph,
    pos,
    width=edgewidth,
    edge_color='b'
  )
  nx.draw_networkx_labels(graph, pos, fontsize=5)
  # сохраняем и показываем визуализированный граф
  plt.savefig('saved')
  plt.show()
```

Вызовем реализованную функцию в `main`.
```python
plot_graph(g, 500)
```
В результате работы кода будет получено изображение графа друзей пользователя.

Данный метод является довольно простым, т.к. при этом не происходит процесс кластеризации графа, для лучшего понимания его структуры. Однако этот пример удобен для понимания процесса работы с графами.