Share |

среда, 7 февраля 2018 г.

Исследование email коммуникаций в компании



Прохожу на Курсере курс Applied Social Network Analysis in Python Мичиганского университета. Вообще рекомендую курсы этого университета: много интересного контента. Курсы на Python проходят.
Прошел я пока две недели, хвастать особо нечем, хочу в посте скорее закрепить свои знания. Заранее предупреждаю, что я не ас, могу писать ерунду в каких-то местах, просто тема для меня новая. А новая потому, что я не могу найти понимания того, куда в HR ее применить. Буду вам благодарен за идеи. Вообще же на Западе тема Organisational Network Analytics очень популярна и набирает темп. Поэтому я и решил подробней в ней разобраться. 

Данные е майл коммуникаций

У нас есть данные типа
#Sender Recipient time
1 2 1262454010
1 3 1262454010
1 4 1262454010
1 5 1262454010
1 6 1262454010
1 7 1262454010
1 8 1262454010
1 9 1262454010
1 10 1262454010
1 11 1262454010

Где первая колонка - отправитель, вторая - получатель, и время отправления. При этом перечень полей не ограничен: это такой датасет в задании, а вообще первое, что приходит на ум - можно добавить роль отправителя и получателя.
И первое, чему я самостоятельно научился по этой теме - раскрашивать узлы (nodes) цветом в зависимости от роли. Как вариант - можно брать не роль, а класс письма: заявка, обращение, нецензурная брань и т.п..
Исследование email коммуникаций в компании
Эта картинка из другого задания.
Если вы хотите потренироваться, запишитесь на курс и скачайте данные второй недели. Или лучше работать со своими данными: можете мне прислать, я буду очень рад поработать с ними.
Код

import networkx as nx
import pandas as pd
%matplotlib notebook
import matplotlib.pyplot as plt
Загрузка
G = nx.read_edgelist('email_network.txt', data=[ ('time', float)], 
                         create_using=nx.MultiDiGraph())
Я не уверен, что стоит подробно функции объяснять, все равно придется копаться в пакете, отмечу только, что самое важное здесь - MultiDiGraph - мы указываем, что отношения между узлами (nodes) директивные (Di), т.е. письмо идет в определенном направлении от кого то к кому то, т.е. у нас на диаграмме появляется стрелочка. И отношения Multi - может быть много связей, а точнее писем. И data - мы указываем, что время - это атрибут сети, а не сами связи. После загрузки мы получаем объект edges (ребра? грани?)
G.edges(data=True)
[('158', '64', {'time': 1271766218.0}),
 ('154', '68', {'time': 1271766200.0}),
 ('42', '54', {'time': 1276505582.0}),
 ('42', '1', {'time': 1266579464.0}),
 ('42', '61', {'time': 1273146267.0}),
 ('42', '151', {'time': 1266319958.0}),
 ('42', '151', {'time': 1268999261.0}), ....
Тут все понятно. Количество строк
len(G.edges(data=True))
82927
И вот здесь сразу одна интересная ситуевина. С помощью команды
G.degree()
{'1': 3376,
 '10': 643,
 '100': 126,
 '101': 758,
 '102': 31,
 '103': 461,
 '104': 632,
 '105': 321,
 '106': 860,
 '107': 192,
 '108': 151,
 '109': 166,
 '11': 635,
 '110': 85,
......

Мы получаем список количества писем каждого чувака. И всего у нас
nx.number_of_nodes(G)
167 
чуваков, а писем
sum(G.degree().values())
165854
Что в два раза больше, чем строк в датасете (82927). Т.е. это значит, что пакет учитывает не только тех, кто слева, но и справа. И если ты значишься не только в числе посылателей писем, но и получателей, вам это будет зачтено.

Визуализация


plt.figure(figsize=(10,9))
node_color = [G.degree(v) for v in G]
pos = nx.fruchterman_reingold_layout(G)
nx.draw_networkx(G, pos, 
                 node_color=node_color, alpha=0.7, with_labels=True, 
                 edge_color='.4', cmap=plt.cm.Blues)
plt.axis('off')
plt.tight_layout();
Цвет узла (node) я определил в зависимости от количества получаемых писем


Исследование email коммуникаций в компании
Не очень внятно, верно?) функция pos = nx.fruchterman_reingold_layout(G) отвечает за представление результатов сети, можете поиграться, картинка будет другая. К тому же можно выделить отдельный диапазон картинки.
Исследование email коммуникаций в компании
Матрица.
Можно менять размер узлов (nodes), убирать грани и т.п. Думаю, понятно: я взял самый центр верхней картинки. И третий чувак у нас кажется самым насыщенным по письмам. Мне кажется, это инженер по технике безопасности или кладовщик.


max_value = max(G.degree().values())
max_key, = [i for i in G.degree().keys() if G.degree()[i] == max_value]
print('чувак {}\n{} писем'.format(max_key, max_value))
чувак 3
9053 писем
Действительно, этот чувак отправил / получил более 9 000 писем.

Метрики

Дальше я покажу несколько метрик сети. Мы проверяем сеть на сильное и слабое соединение.
nx.is_strongly_connected(G)
nx.is_weakly_connected(G)
Строгое соединение такое соединение, при котором любой узел (node) может отправить письмо любому узлу и получить письмо от любого узла, т.е. стрелочки идут во все концы, а слабое - соединение, при котором от каждого узла хотя бы в одну сторону отходит стрелочка.
Машина говорит, что у нас нет строгого соединения, и мы получаем список тех, кто в составе сильного субграфа (где все со всеми) и отщепенцы

sorted(nx.strongly_connected_components(G))
[{'133'},
 {'102'},
 {'126'},
 {'125'},
 {'136'},
 {'132'},
 {'114'},
 {'130'},
 {'127'},
 {'135'},
 {'131'},
 {'116'},
 {'166'},
 {'1',
  '10',
  '100',
  '101',
  '103',
  '104',
  '105',
  '106',
  '107',
  '108',
Обратили внимание, что 102, 126 и т.д. это отщепенцы, а 1, 10, 100 и т.д.... это чуваки не выделяющиеся из массы. С помощью команды

G_sc = max(nx.strongly_connected_component_subgraphs(G), key=len)
мы выделяем субграф, где все связаны со всеми, и дальше работаем с ним, как с отдельным графом. И обращаю внимание: этот граф мы уже записываем не как директивный и мульти. В этом графе у нас такие характеристики:

nx.average_shortest_path_length(G_sc)
1.6461587301587302
Я дам определение с курса: Average distance between every pair of nodes. Т.е. каждая пара узлов (nodes) может быть связана расстоянием 1 - когда узлы связаны напрямую, 2 - когда между ними кто-то есть еще и т.д.. Так вот average_shortest_path_length показывает среднее расстояние по компании. Фактически тесность связей.

nx.diameter(G_sc)
3
Помните про теорию 6 рукопожатий? в данном случае диаметр означает самую длинную связь в компании. Т.е. между самыми дальними чуваками всего три, а не шесть ребер, связей....

Можно посмотреть на каждый узел с т.з. длины связей
nx.eccentricity(G_sc)
{'1': 1,
 '10': 2,
 '100': 2,
 '101': 2,
 '103': 2,
 '104': 2,
 '105': 2,
 '106': 2,
 '107': 3,
....
Т.е. первый чувак со всеми на короткой ноге, а вот 107-й не так чтобы и т.п. А также мы можем вычислить радиус

nx.radius(G_sc)
1
Это минимально длинная связь. Т.е. если 3 говорит нам, что самая длинная связь в компании это три, то есть чуваки, которые со всеми связаны через одну связь, т.е. напрямую. И нам осталось вычислить тех, кто имеет самые длинные связи (т.е. это те, кто менее всего включены в коммуникации в компании):
nx.periphery(G_sc)
['134', '129', '97']
Вот эти три чувака менее включены. Периферия. А центровые, самые включенные
nx.center(G_sc)
['1', '38'] 
Т.е. по сути это это самые включенные люди. Но сразу предупреждаю - это не робастная метрика. Дальше в курсе обещали показать более надежные характеристики. И в качестве примера. Можно, например, вычислить чувака, у которого больше всего длинных связей (этот будет чувак из списка 134, 129, и 97) и с кем этот чувак в самых длинных связях:
%%time
y = []
for i in nx.periphery(G_sc):
    z = sum(j == nx.diameter(G_sc) for j in nx.shortest_path_length(G_sc,i).values() )
    y.append([i,z])
maxex = nx.shortest_path_length(G_sc,max(y)[0])
p = { key:value for key, value in maxex.items() if value == nx.diameter(G_sc) }
print('чувак {}\n чуваки {} '.format(max(y)[0], list(p.keys())))  
чувак 97
чуваки ['30', '78', '106', '121', '46', '41', '33', '109', '93', '6', '110', '29', '24', '82', '60', '19', '151', '124', '49', '43', '76', '129',
 '55', '96', '27', '48', '75', '81', '119', '122', '123', '149', '99', '8', '107', '105', '98', '39', '134', '128', '111', 
'28', '118', '101', '21', '83', '34', '120', '57', '16', '20', '52', '26', '51', '91', '23', '89', '108', '40', '87', '79', '86', '18'] 
Wall time: 29.1 s
Из того, что еще можно вытащить: можно вытащить слабые места графа - те узлы (node), после которых граф распадается как целостный граф.
Ну вот примерно так. Еще раз скажу, что пока не понимаю, что из этого можно выжать для предиктивной HR аналитики. Буду рад вам за советы. Все, что возникает в голове: посмотреть динамику связей новичков после приема - нам это может что-то сказать про адаптация специалиста. Правда, я не представляю себе HR службу, которая бы всерьез этим бы занималась. Или, из прошлого: у нас в банке в конце года филиалы оценивали взаимодействие с центральными службами: IT, бухгалтерией, HR и т.п. - вот эти результаты можно было бы сопроводить подобной перепиской. Можно посмотреть связь показателей эффективности и характером е майл коммуникаций..... ну и т.п..
В любом случае буду рад, если вы мне пришлете свои датасеты, может вместе что-нибудь накопаем.



__________________________________________________________
На этом все, читайте нас в фейсбукетелеграмме и вконтакте

Комментариев нет:

Отправить комментарий

Популярные сообщения

п