У меня есть фрейм данных матчей онлайн-игр, включающий два конкретных столбца: идентификаторы матчей и идентификаторы игроков, участвовавших в конкретном матче. Например:
Следовательно, player_id — это уникальный идентификатор игрока. Между тем, match_id — это идентификатор сыгранного матча, и он дублируется фиксированное количество раз (скажем, 5), так как 5 — это максимальное количество игроков, которые могут участвовать в определенном матче. Таким образом, в каждой строке match_id соответствует player_id, что означает, что определенный игрок участвовал в определенной игре.
Как видно из таблицы выше, двое и более игроков могут играть вместе более одного раза (или вообще не иметь совместной игры). И именно поэтому я заинтересован в преобразовании этого исходного фрейма данных в матрицу смежности, в которой пересечение строки и столбца даст количество сыгранных матчей. Другим вариантом было бы создать фрейм данных, как показано ниже:
Таким образом, моя задача — подготовить данные для дальнейшего анализа сети совместных игр с использованием igraph или networkx. Я также хочу получить взвешенную сеть, то есть вес ребра будет означать количество совместных матчей между двумя узлами (игроками). Преимущество в этом случае означает, что два пользователя играли вместе, то есть они участвовали в одном и том же матче один раз или играли вместе как команда в двух или более матчах (например, идентификаторы игроков 1 и 2 в примере исходных данных выше).
Мой вопрос: как я могу преобразовать свой исходный фрейм данных в сетевые данные, которые функции igraph или networkx будут принимать в качестве аргумента, используя pandas и numpy? А может мне не нужны никакие манипуляции с данными и функции igraph или networkx умеют работать с исходным фреймом данных?
Заранее спасибо за ответы и рекомендации!
Я думаю, вам не нужно networkx, если вы используете permutations из itertools и pd.crosstab:
from itertools import permutations
pairs = (df.groupby('match_id')['player_id']
.apply(lambda x: list(permutations(x, r=2)))
.explode())
adj = pd.crosstab(pairs.str[0], pairs.str[1],
rownames=['Player 1'], colnames=['Player 2'])
Выход:
>>> adj
Player 2 1 2 3 4 5 6 7 8
Player 1
1 0 2 1 1 1 1 1 1
2 2 0 1 1 1 1 1 1
3 1 1 0 1 1 0 0 0
4 1 1 1 0 1 0 0 0
5 1 1 1 1 0 0 0 0
6 1 1 0 0 0 0 1 1
7 1 1 0 0 0 1 0 1
8 1 1 0 0 0 1 1 0
Если вам нужен плоский список (а не матрица смежности), используйте combinations:
from itertools import combinations
pairs = (df.groupby('match_id')['player_id']
.apply(lambda x: frozenset(combinations(x, r=2)))
.explode().value_counts())
coplays = pd.DataFrame({'Player 1': pairs.index.str[0],
'Player 2': pairs.index.str[1],
'coplays_number': pairs.tolist()})
Выход:
>>> coplays
Player 1 Player 2 coplays_number
0 1 2 2
1 2 4 1
2 6 2 1
3 8 2 1
4 7 2 1
5 1 7 1
6 6 7 1
7 1 8 1
8 6 8 1
9 6 1 1
10 3 5 1
11 1 3 1
12 2 5 1
13 4 5 1
14 2 3 1
15 1 4 1
16 1 5 1
17 3 4 1
18 7 8 1
Что такое exp в вашем первом блоке кода?
Извините, исправил. Старая переменная, это было pairs :-)
Вы можете объединить свой начальный df с самим собой на match_id. Затем сгруппируйте по player_1, player_2 и size(), чтобы получить фрейм данных с взвешенными краями.
df.merge(df, how='inner', on='match_id', suffixes=('1', '2'))\
.groupby(['player_id1', 'player_id2'], as_index=False).size()
Вы также получите строки, где player_id1 == player_id2: это будет общее количество матчей, в которых сыграл игрок.
import pandas as pd
import networkx as nx
a, b, c = 'a', 'b', 'c'
df = pd.DataFrame(
{
'match_id': [0, 0, 0, 1, 1, 2],
'player_id': [a, b, c, a, b, c],
})
print(df)
match_id player_id
0 0 a
1 0 b
2 0 c
3 1 a
4 1 b
5 2 c
edges = df.merge(df, on='match_id', how='inner', suffixes=('1', '2'))\
.groupby(['player_id1', 'player_id2'], as_index=False).size()
print(edges)
player_id1 player_id2 size
0 a a 2
1 a b 2
2 a c 1
3 b a 2
4 b b 2
5 b c 1
6 c a 1
7 c b 1
8 c c 2
graph = nx.from_pandas_edgelist(edges, source='player_id1', target='player_id2',
edge_attr='size', create_using=nx.Graph)
pos = nx.spring_layout(graph)
nx.draw_networkx(graph, pos, with_labels=True)
nx.draw_networkx_edge_labels(graph, pos, edge_labels=nx.get_edge_attributes(graph,'size'))
Дает
Вы можете использовать create_using=nx.DiGraph, чтобы получить:
Networkx не рисует это, но петли взвешиваются:
>>> graph['a']['a']
{'size': 2}
Вы должны удалить дубликаты, когда player_1 равен player_2. И отбросить (1, 2) или (2, 1) и так далее.
@Corralien нет, если вы хотите сохранить количество игр, в которых играл игрок
Хорошее обновление с networkx.
Igraph имеет функцию для приема pandas.DataFrame, содержащего ребра и веса, и построения из него графика.
Чтобы подготовить фрейм данных края, вы можете объединить df с самим собой, как показано в другом ответе, это законно. Однако, если данные большие, вам может не хватить памяти. Кроме того, график не направлен, поэтому вы можете записать каждый контакт игрока только один раз. Для разнообразия, вот как бы я это сделал:
from collections import Counter
import pandas as pd
import igraph as ig
# 1. Make a nonredundant counter for player contacts
edge_dict = Counter()
for _, group in df.groupby('match_id'):
players = group['player_id']
for i1, p1 in enumerate(players):
for p2 in players[:i1]:
edge_dict[(min(p1, p2), max(p1, p2))] += 1
# 2. Convert the counter to an edge DataFrame
edges = pd.Series(edge_dict, name='weight').to_frame().reset_index()
# 3. Ingest the DataFrame into igraph, including weights
g = ig.Graph.DataFrame(
edges,
use_vids=False)
Это так интересно :-)