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

У меня есть фрейм данных матчей онлайн-игр, включающий два конкретных столбца: идентификаторы матчей и идентификаторы игроков, участвовавших в конкретном матче. Например:

match_id player_id 0 1 0 2 0 3 0 4 0 5 1 6 1 1 1 7 1 8 1 2

Следовательно, player_id — это уникальный идентификатор игрока. Между тем, match_id — это идентификатор сыгранного матча, и он дублируется фиксированное количество раз (скажем, 5), так как 5 — это максимальное количество игроков, которые могут участвовать в определенном матче. Таким образом, в каждой строке match_id соответствует player_id, что означает, что определенный игрок участвовал в определенной игре.

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

player_1 player_2 coplays_number 1 2 2 1 3 1 1 4 1 1 10 0 1 5 1 ... ... ...

Таким образом, моя задача — подготовить данные для дальнейшего анализа сети совместных игр с использованием igraph или networkx. Я также хочу получить взвешенную сеть, то есть вес ребра будет означать количество совместных матчей между двумя узлами (игроками). Преимущество в этом случае означает, что два пользователя играли вместе, то есть они участвовали в одном и том же матче один раз или играли вместе как команда в двух или более матчах (например, идентификаторы игроков 1 и 2 в примере исходных данных выше).

Мой вопрос: как я могу преобразовать свой исходный фрейм данных в сетевые данные, которые функции igraph или networkx будут принимать в качестве аргумента, используя pandas и numpy? А может мне не нужны никакие манипуляции с данными и функции igraph или networkx умеют работать с исходным фреймом данных?

Заранее спасибо за ответы и рекомендации!

Это так интересно :-)

Corralien 22.02.2023 21:51
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Привет, люди RPA, это снова я и я несу подарки! В очередном моем приключении о том, как создавать ботов для облегчения рутины. Вот, думаю, стоит...
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
Учебник по веб-скрапингу
Учебник по веб-скрапингу
Привет, ребята... В этот раз мы поговорим о веб-скрейпинге. Целью этого обсуждения будет узнать и понять, что такое веб-скрейпинг, а также узнать, как...
Тонкая настройка GPT-3 с помощью Anaconda
Тонкая настройка GPT-3 с помощью Anaconda
Зарегистрируйте аккаунт Open ai, а затем получите ключ API ниже.
2
1
56
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Я думаю, вам не нужно 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 в вашем первом блоке кода?

politinsa 22.02.2023 22:07

Извините, исправил. Старая переменная, это было pairs :-)

Corralien 22.02.2023 22:09

Вы можете объединить свой начальный 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 22.02.2023 22:03

@Corralien нет, если вы хотите сохранить количество игр, в которых играл игрок

politinsa 22.02.2023 22:05

Хорошее обновление с networkx.

Corralien 22.02.2023 23:09

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)

Другие вопросы по теме