【问题标题】:Creating a pandas pivot table to count number of times items appear in a list together创建一个熊猫数据透视表来计算项目一起出现在列表中的次数
【发布时间】:2021-02-03 22:14:15
【问题描述】:

我正在尝试计算用户在同一会话中查看页面的次数。

我从一个列出 user_ids 和他们访问过的页面 slug 的数据框开始:

user_id page_view_page_slug
1       slug1
1       slug2
1       slug3
1       slug4
2       slug5
2       slug3
2       slug2
2       slug1

我希望得到的是一个数据透视表,计算 slugs 横截面的 user_ids

. slug1 slug2 slug3 slug4 slug5
slug1 2 2 2 1 1
slug2 2 2 2 1 1
slug3 2 2 2 1 1
slug4 1 1 1 1 0
slug5 1 1 1 0 1

我意识到当我们看到 slug1 和 slug2 与 slug2 和 slug1 时,这将是相同的数据,但我想不出更好的方法。 到目前为止我已经做了一个listagg

def listagg(df, grouping_idx):
    return df.groupby(grouping_idx).agg(list)
new_df = listagg(df,'user_id')

返回:

          page_view_page_slug
user_id                                                   
1        [slug1, slug2, slug3, slug4]
2        [slug5, slug3, slug2, slug2]
7        [slug6, slug4, slug7]
9        [slug3, slug5, slug1]

但是我很难想到当项目一起出现在列表中时(尽管有顺序)以及如何存储它来计算循环。然后我也不知道如何以可旋转的格式获得它。

【问题讨论】:

  • 你试过df.pivot吗?
  • 数据是否有重复的可能?例如,如果(user_id 1, slug1) 重复,那么结果应该是什么?对于当前的两个答案,它要么为slug1(重复)创建额外的行和列,要么添加两次,第一行和第一列变为[5, 3, 3, 2, 1],我认为这更准确。

标签: python pandas numpy pivot-table


【解决方案1】:

这是另一种使用 numpy 广播创建矩阵的方法,该矩阵通过将user_id 中的每个值与其他所有值进行比较而获得,然后从该矩阵创建一个新的数据帧,并将indexcolumns 设置为@ 987654325@ 和level=0 上的sum 沿着axis=0axis=1 计算蛞蝓横截面的user_ids

a = df['user_id'].values
i = list(df['page_view_page_slug'])

pd.DataFrame(a[:, None] == a, index=i, columns=i)\
   .sum(level=0).sum(level=0, axis=1).astype(int)

       slug1  slug2  slug3  slug4  slug5
slug1      2      2      2      1      1
slug2      2      2      2      1      1
slug3      2      2      2      1      1
slug4      1      1      1      1      0
slug5      1      1      1      0      1

【讨论】:

  • 我觉得这个比较好,因为if有重复,比如(user_id 1, slug1),顶行&第一列变成[5, 3, 3, 2, 1],我觉得是更准确的。在您的另一个答案中,它为slug1(重复)创建了一个额外的行和列。 (虽然,我认为[3, 3, 3, 2, 1] 在这种情况下是正确的结果。) 等待 OP 确认 if there can be repetitions 以及结果应该是什么样子。
  • 谢谢@aneroid 是的,这取决于 OP,虽然我没有检查重复时的结果,但我猜 [5, 3, 3, 2, 1] 似乎更正确,真的感谢您测试代码:)
【解决方案2】:

让我们试试groupbyreduce

from functools import reduce

dfs = [pd.DataFrame(1, index=list(s), columns=list(s)) 
      for _, s in df.groupby('user_id')['page_view_page_slug']]
      
df_out = reduce(lambda x, y: x.add(y, fill_value=0), dfs).fillna(0).astype(int)

详情:

group user_id 上的数据框然后为page_view_page_slug 中的每个组每个user_id 创建一个邻接数据框,其索引和列对应于该组中的slugs

>>> dfs

[       slug1  slug2  slug3  slug4
 slug1      1      1      1      1
 slug2      1      1      1      1
 slug3      1      1      1      1
 slug4      1      1      1      1,
        slug5  slug3  slug2  slug1
 slug5      1      1      1      1
 slug3      1      1      1      1
 slug2      1      1      1      1
 slug1      1      1      1      1]

现在reduce 上述邻接数据帧使用缩减函数DataFrame.add 和可选参数fill_value=0 来计算slugs 横截面的user_ids。

>>> df_out

       slug1  slug2  slug3  slug4  slug5
slug1      2      2      2      1      1
slug2      2      2      2      1      1
slug3      2      2      2      1      1
slug4      1      1      1      1      0
slug5      1      1      1      0      1

可选您可以将上述代码包装在一个函数中,如下所示:

def count():
    df_out = pd.DataFrame()
    for _, s in df.groupby('user_id')['page_view_page_slug']:
        df_out = df_out.add(
            pd.DataFrame(1, index=list(s), columns=list(s)), fill_value=0)

    return df_out.fillna(0).astype(int)

>>> count()

       slug1  slug2  slug3  slug4  slug5
slug1      2      2      2      1      1
slug2      2      2      2      1      1
slug3      2      2      2      1      1
slug4      1      1      1      1      0
slug5      1      1      1      0      1

【讨论】:

  • 不错的一个。而且看不到pivot! +1(因为很明显,简单的支点并不能满足需要。)s 是 OP 顶部的系列版本吗?
  • @aneroid 谢谢! spage_view_page_sluguser_id 所以例如对于 user_id=1 s 将是包含 slug 值的系列 ['slug1', 'slug2', 'slug3', 'slug4'] 同样对于 user_id=2 s 将是一个系列包含值 ['slug5', 'slug3', 'slug2', 'slug1']
【解决方案3】:

让我们在 user_id 上使用 mergepd.crosstab 的自联接来计数:

import pandas as pd
from io import StringIO

txt = StringIO("""user_id  page_view_page_slug
1       slug1
1       slug2
1       slug3
1       slug4
2       slug5
2       slug3
2       slug2
2       slug1""")

df = pd.read_csv(txt, sep='\s\s+')

dfm = df.merge(df, on='user_id')
df_out = pd.crosstab(dfm['page_view_page_slug_x'], dfm['page_view_page_slug_y'])

df_out

输出:

page_view_page_slug_y  slug1  slug2  slug3  slug4  slug5
page_view_page_slug_x                                   
slug1                      2      2      2      1      1
slug2                      2      2      2      1      1
slug3                      2      2      2      1      1
slug4                      1      1      1      1      0
slug5                      1      1      1      0      1

对于重复数据,我们试试:

dfi = df.assign(v_count=df.groupby(['user_id', 'page_view_page_slug']).cumcount())

#Let's filter some unnecessary joins with query
dfi = dfi.merge(dfi, on=['user_id'])\
         .query('page_view_page_slug_x != page_view_page_slug_y or page_view_page_slug_x == page_view_page_slug_y and v_count_x == v_count_y')

df_out = pd.crosstab(dfi['page_view_page_slug_x'], dfi['page_view_page_slug_y'])
df_out

输出:

page_view_page_slug_y  slug1  slug2  slug3  slug4  slug5
page_view_page_slug_x                                   
slug1                      3      3      3      2      1
slug2                      3      2      2      1      1
slug3                      3      2      2      1      1
slug4                      2      1      1      1      0
slug5                      1      1      1      0      1

【讨论】:

  • 只有一个合并和一个交叉表。好的!没想到自己加入+1。如果 OP 回应 repetition of data,理想的改变是什么?目前,它将[5, 3, 3, 2, 1] 作为第一行。
  • 选择这个作为赏金答案,因为它只适用于mergecrosstab,而且为了获得所需的结果而不太费心。
猜你喜欢
  • 1970-01-01
  • 2018-11-30
  • 1970-01-01
  • 1970-01-01
  • 2021-02-06
  • 2018-05-05
  • 2020-10-09
  • 2019-02-12
  • 1970-01-01
相关资源
最近更新 更多