Подсчитать вхождение двух элементов в столбец списка - PullRequest
0 голосов
/ 11 июня 2018

Я боролся с этим уже несколько дней.Я много читал в Интернете, нашел несколько похожих вопросов, таких как: Панды, подсчитывающие вхождение списка, содержащегося в столбце списков или Панды: подсчитать строковые критерии по нижним строкам , но ни одна из них не работает в этомcase.

У меня есть два кадра данных: df1 состоит из столбца строк.df2 состоит из столбца списков (списки представляют собой комбинацию строк из df1, каждый элемент в одном списке уникален).

Я хотел бы знать, во сколько списках df2 встречается каждая комбинация строк,Итак, сколько списков имеют «a» и «b» в качестве элементов?Сколько списков имеют «a» и «c» в качестве элементов и т. Д.

Вот так выглядит df1 (упрощенно):

df1 = pd.DataFrame({"subject": ["a", "b", "c"]})

df1
    subject
0   a
1   b
3   c

Так выглядит df2 (упрощенно)).

df2 = pd.DataFrame({"subject_list": [["a", "b" ,"c"], ["b", "c"], ["a", "b"], ["b", "c"], ["c"]]})

df2

     subject_list
0    ["a", "b" ,"c"]
1    ["a", "b"] 
2    ["b", "c"]
3    ["c"]
4    ["b", "c"]

У меня есть два кода, которые оба работают, но не совсем правы:

Этот код ищет комбинацию двух строк в df1 (как и хотелось).Однако df1 включает в себя больше строк, чем df2, поэтому он останавливается на последней строке df2.Но есть еще несколько «строковых комбинаций» для проверки.

df1["combination_0"] = df2["subject_list"].apply(lambda x: x.count(x and df.subject[0]))

Этот код считает появление одного «списка».Однако я не могу понять, как изменить это так, чтобы он делал это для каждой комбинации значений.

df1["list_a_b"] = df2["subject_list"].apply(lambda x: x.count(df1.subject[0] and df1.subject[1]))
df1.list_a_b.sum()

Ответы [ 5 ]

0 голосов
/ 11 июня 2018

Вот еще один подход.Ниже приведены две основные идеи:

  1. Мы можем начать с пересечения каждого списка в df2 со значениями df1.Таким образом, мы можем избежать рассмотрения избыточных подмножеств каждой строки df2.

  2. После шага 1, df2 может содержать дублированные наборы.Сбор дубликатов может ускорить оставшиеся вычисления.

Оставшаяся задача - рассмотреть каждое подмножество df1 и подсчитать количество вхождений.


import pandas as pd
import numpy as np
from itertools import combinations
from collections import Counter

df1 = pd.DataFrame({"subject": ["a", "b", "c"]})

df2 = pd.DataFrame(
    {
        "subject_list": [
            ["a", "b", "c", "x", "y", "z", "1", "2", "3"],
            ["b", "c"],
            ["a", "b"],
            ["b", "c"],
            ["c"],
        ]
    }
)

s1 = set(df1.subject.values)


def all_combs(xs):
    for k in range(1, len(xs) + 1):
        yield from combinations(xs, k)


def count_combs(xs):
    return Counter(all_combs(xs))


res = (
    df2.subject_list.apply(s1.intersection)
    .apply(frozenset)
    .value_counts()
    .reset_index()
)

# (b, c)       2
# (c, b, a)    1
# (c)          1
# (b, a)       1

res2 = res["index"].apply(df1.subject.isin).mul(res.subject_list, axis=0)
res2.columns = df1.subject

# subject  a  b  c
# 0        0  2  2
# 1        1  1  1
# 2        0  0  1
# 3        1  1  0

res3 = pd.Series(
    {
        "_".join(comb): res2[comb][(res2[comb] > 0).all(1)].sum(0).iloc[0]
        for comb in map(list, all_combs(df1.subject.values))
    }
)


# a        2
# b        4
# c        4
# a_b      2
# a_c      1
# b_c      3
# a_b_c    1
# dtype: int64
0 голосов
/ 11 июня 2018

Вот решение, которое я попробовал.

Начиная с двух имеющихся у вас информационных фреймов, вы можете использовать itertools для получения всех возможных комбинаций элементов df1 два на два:

import itertools

df1 = pd.DataFrame({"subject": ["a", "b", "c"]})
df2 = pd.DataFrame({"subject_list": [["a", "b", "c"], ["b", "c"], ["a", "b"], ["b", "c"], ["c"]]})

# Create a new dataframe with one column that has the possible two by two combinations from `df1`
df_combinations = pd.DataFrame({'combination': list(itertools.combinations(df1.subject, 2))})

Затем переберите новый фрейм данных, df_combinations в этом случае, чтобы узнать, сколько раз каждая комбинация встречается в df2:

for index, row in df_combinations.iterrows():

    df_combinations.at[index, "number of occurrences"] = df2["subject_list"].apply(lambda x: all(i in x for i in row['combination'])).sum()

Основное различие в этом шаге в отношенииВаше первоначальное решение состоит в том, что я не использую x.count, а скорее all, поскольку этот гарантирует, что будут учитываться только те экземпляры, в которых присутствуют оба значения.

Наконец df_combinations:

  combination  number of occurrences
0      (a, b)                    2.0
1      (a, c)                    1.0
2      (b, c)                    3.0
0 голосов
/ 11 июня 2018

Вот моя попытка решить вашу проблему.

Существует два основных шага:

  • создать все возможные списки для проверки из значений df1
  • подсчитать, сколько строк в df2 содержит каждую комбинацию

Код:

import itertools

def all_in(elements, a_list):
    # Check if all values in the list elements are present in a_list
    return all(el in a_list for el in elements)

# All the (unique) values in df1
all_values = sorted(set(df1.sum()['subject']))

result = pd.Series()

# For each sequence length (1, 2, 3)
for length in range(1, len(all_values)+1):
    # For each sequence of fixed length
    for comb in itertools.combinations(all_values, length):
        # Count how many rows of df2 contains the sequence
        result["_".join(comb)] = df2.squeeze().apply(lambda x: all_in(comb, x)).sum()

, что дает:

result

a        2
b        4
c        4
a_b      2
a_c      1
b_c      3
a_b_c    1

В зависимости от размера фактических данныхи по вашим требованиям вы могли бы сделать вещи умнее.Например, если вы знаете, что 'a' отсутствует в строке, вы автоматически назначите False для любой комбинации, включая 'a'

0 голосов
/ 11 июня 2018

Это решение не от Pandas, использующее collections.defaultdict и itertools.combinations.Логика состоит из 2 частей:

  1. Рассчитать все комбинации из df1['subject'].
  2. Итерировать df2['subject_list'] и увеличить счетчик словаря.

frozenset используется целенаправленно, поскольку они являются хэшируемыми и указывают, как и в вашем вопросе, что порядок не имеет значения.

from collections import defaultdict
from itertools import combinations

df1 = pd.DataFrame({"subject": ["a", "b", "c"]})
df2 = pd.DataFrame({"subject_list": [["a", "b" ,"c"], ["b", "c"], ["a", "b"], ["b", "c"], ["c"]]})

# calculate all combinations
combs = (frozenset(c) for i in range(1, len(df1.index)+1) \
         for c in combinations(df1['subject'], i))

# initialise defaultdict
d = defaultdict(int)

# iterate combinations and lists
for comb in combs:
    for lst in df2['subject_list']:
        if set(lst) >= comb:
            d[comb] += 1

print(d)

defaultdict(int,
            {frozenset({'a'}): 2,
             frozenset({'b'}): 4,
             frozenset({'c'}): 4,
             frozenset({'a', 'b'}): 2,
             frozenset({'a', 'c'}): 1,
             frozenset({'b', 'c'}): 3,
             frozenset({'a', 'b', 'c'}): 1})
0 голосов
/ 11 июня 2018

Эта проблема несколько сложна, потому что в зависимости от того, сколько у вас значений, может быть много парных сравнений.Я думаю, что вы можете захотеть создать манекен df с манекенами для каждого значения, а затем вы можете использовать .all для простого запроса любой парной комбинации, которую вы хотите.Это также легко обобщить, если вам нужны комбинации любого количества элементов.

Сначала создайте df_dummy, который указывает, содержится ли это значение в списке.

df_dummy = df2.subject_list.str.join(sep='?').str.get_dummies(sep='?')
#   a  b  c
#0  1  1  1
#1  0  1  1
#2  1  1  0
#3  0  1  1
#4  0  0  1

Затем создайте список всех парных комбинаций, которые вам нужно сделать (игнорируя порядок)и те же значения

vals = df1.subject.unique()
combos = list((vals[j], vals[i]) for i in range(len(vals)) for j in range(len(vals)) if i>j)
print(combos)
#[('a', 'b'), ('a', 'c'), ('b', 'c')]

Теперь проверьте все парные комбинации:

for x, y in combos:
    df2[x+'_and_'+y]=df_dummy[[x, y]].all(axis=1)

df2:

  subject_list  a_and_b  a_and_c  b_and_c
0    [a, b, c]     True     True     True
1       [b, c]    False    False     True
2       [a, b]     True    False    False
3       [b, c]    False    False     True
4          [c]    False    False    False

Если вы хотите сосчитатьитого, затем просто используйте sum, игнорируя первый столбец

df2[df2.columns[1:]].sum()
#a_and_b    2
#a_and_c    1
#b_and_c    3
#dtype: int64
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...