【问题标题】:Count if in multiple index Dataframe计数是否在多个索引数据框中
【发布时间】:2020-10-23 09:00:34
【问题描述】:

我有一个多索引数据框,我想知道按以下 3 个标准(城市、信用卡和抵押品)中的每一个支付一定债务门槛的客户百分比。

这是一个工作脚本:

import pandas as pd

d = {'City': ['Tokyo','Tokyo','Lisbon','Tokyo','Tokyo','Lisbon','Lisbon','Lisbon','Tokyo','Lisbon','Tokyo','Tokyo','Tokyo','Lisbon','Tokyo','Tokyo','Lisbon','Lisbon','Lisbon','Tokyo','Lisbon','Tokyo'], 
     'Card': ['Visa','Visa','Master Card','Master Card','Visa','Master Card','Visa','Visa','Master Card','Visa','Master Card','Visa','Visa','Master Card','Master Card','Visa','Master Card','Visa','Visa','Master Card','Visa','Master Card'],
     'Colateral':['Yes','No','Yes','No','No','No','No','Yes','Yes','No','Yes','Yes','No','Yes','No','No','No','Yes','Yes','No','No','No'],
     'Client Number':[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22],
     '% Debt Paid':[0.8,0.1,0.5,0.30,0,0.2,0.4,1,0.60,1,0.5,0.2,0,0.3,0,0,0.2,0,0.1,0.70,0.5,0.1]}

df = pd.DataFrame(data=d)

df1 = (df.set_index(['City','Card','Colateral'])
         .drop(['Client Number'],axis=1)
        .sum(level=[0,1,2]))

df2 = df1.reindex(pd.MultiIndex.from_product(df1.index.levels), fill_value=0)

结果如下:

为了克服这个问题,我尝试了以下方法但没有成功:


df1 = (df.set_index(['City','Card','Colateral'])
        .drop(['Client Number'],axis=1)
       [df.Total = 0].count(level=[0,1,2])/[df.Total].count()
       [df.Total > 0 & df.Total <=0.25 ].count(level=[0,1,2])/[df.Total].count()
       [df.Total > 0.25 & df.Total <=0.5 ].count(level=[0,1,2])/[df.Total])
       [df.Total > 0.5 & df.Total <=0.75 ].count(level=[0,1,2])/[df.Total]
       [df.Total > 0.75 & df.Total <1 ].count(level=[0,1,2])/[df.Total]
       [df.Total = 1].count(level=[0,1,2])/[df.Total]
       [df.Total > 1].count(level=[0,1,2])/[df.Total])

df2 = df1.reindex(pd.MultiIndex.from_product(df1.index.levels), fill_value=0)

这是我希望针对所有标准实现的结果。 关于如何解决这个问题的任何想法?谢谢。

【问题讨论】:

  • 你接受了 YOBEN_S 的答案,你能解释一下为什么你添加了赏金以及为什么这个答案不起作用?
  • ''当它没有任何信息时,它是否可能在分支上显示 0?''
  • 您能否将其添加到您的问题中,并在接受的答案失败时显示一些示例数据/输出?这些信息对我和你都有帮助!

标签: python python-3.x pandas dataframe multi-index


【解决方案1】:

TL;DR

group_cols = ['City', 'Card', 'Colateral']
debt_col = '% Debt Paid'

# (1) Bin the data that is in non-zero-width intervals
bins = pd.IntervalIndex.from_breaks((0, 0.25, 0.5, 0.75, 1, np.inf),
                                    closed='right')
ser_pt1 = df.groupby(group_cols, sort=False)[debt_col]\
    .value_counts(bins=bins, sort=False, normalize=True)

# (2) Get the data from zero width intervals (0% and 100%)
ser_pt2 = df[df[debt_col].isin((0, 1))]\
        .groupby(group_cols)[debt_col].value_counts()

# Take also "zero counts" and normalize
ser_pt2 = ser_pt2.reindex(
    pd.MultiIndex.from_product(ser_pt2.index.levels,
                               names=ser_pt2.index.names),
    fill_value=0) / df.groupby(group_cols)[debt_col].count()

# (3) Combine the results
ser_out = pd.concat([ser_pt1, ser_pt2])

这是一个简单粗暴的答案。下面是一个可复制粘贴的完整答案,它还按照问题的要求制作索引名称和排序。

1。总结

问题变得更难解决,因为您想要的箱子是相交的。也就是说,您希望拥有]75, 100][100, 100] 的bin,它们都应该包括% Debt Paid1.0 的情况。我将分别处理两种情况

(1)   值 ]0, 25]%]25, 50]%、...、]100%, np.inf]%
(2)  0%100%

2。解决方案说明

2.1 分箱部分

2.2 其余部分(0% 和 100%)

  • 使用pd.Series.isindf[debt_col].isin((0, 1)) 只选择0.01.0 情况,然后使用value_counts 统计“0%”和“100%”的出现次数。
  • 然后,我们还需要包括计数为零的情况。这可以通过重新索引来完成。因此,我们使用pd.Series.reindex 为每个(“City”、“Card”、“Colateral”)组合指定一行,并与pd.MultiIndex.from_product 形成组合
  • 最后,我们通过除以每组中的总计数来标准化计数 (df.groupby(group_cols)[debt_col].count())

2.3 重命名

  • 我们的新索引(级别 3,称为“bin”)现已准备就绪,但要获得与 OP 问题中相同的输出,我们需要重命名索引标签。只需遍历值并使用“查找字典”查找新名称即可完成此操作
  • 默认情况下,索引中标签的顺序取自数字/字母顺序,但这不是我们想要的。要在排序后强制索引顺序,我们必须使用pd.Categorical 作为索引。排序顺序在categories 参数中给出。我们依赖于在 python 3.6+ 字典中保留排序的事实。
  • 出于某种原因,ser_out.sort_index() 即使使用分类索引也无法正常工作。我认为这可能是熊猫中的一个错误。因此,将结果 Series ser_out 强制转换为 DataFrame df_out,并使用 dataframe 进行排序。
  • 最后,生成的数据帧使用 set_index 制成 MultiIndex。

代码

零宽度的 bin 会导致 value_counts 产生非常奇怪的结果。也许这是熊猫的错误​​。因此,我们将问题分为两步(1)统计非零宽度bin中的数据(2)统计零宽度bin(“0%”和“100%”)中的数据

import pandas as pd
import numpy as np

d = {'City': ['Tokyo','Tokyo','Lisbon','Tokyo','Tokyo','Lisbon','Lisbon','Lisbon','Tokyo','Lisbon','Tokyo','Tokyo','Tokyo','Lisbon','Tokyo','Tokyo','Lisbon','Lisbon','Lisbon','Tokyo','Lisbon','Tokyo'], 
     'Card': ['Visa','Visa','Master Card','Master Card','Visa','Master Card','Visa','Visa','Master Card','Visa','Master Card','Visa','Visa','Master Card','Master Card','Visa','Master Card','Visa','Visa','Master Card','Visa','Master Card'],
     'Colateral':['Yes','No','Yes','No','No','No','No','Yes','Yes','No','Yes','Yes','No','Yes','No','No','No','Yes','Yes','No','No','No'],
     'Client Number':[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22],
     '% Debt Paid':[0.8,0.1,0.5,0.30,0,0.2,0.4,1,0.60,1,0.5,0.2,0,0.3,0,0,0.2,0,0.1,0.70,0.5,0.1]}


df = pd.DataFrame(data=d)


def _get_binned_part(df, group_cols, debt_col):
    bins = pd.IntervalIndex.from_breaks((0, 0.25, 0.5, 0.75, 1, np.inf),
                                        closed='right')
    gp = df[group_cols + [debt_col]].groupby(group_cols, sort=False)
    ser_pt1 = gp[debt_col].value_counts(bins=bins, sort=False, normalize=True)
    ser_pt1.index.set_names('bin', level=3, inplace=True)
    return ser_pt1


def _get_non_binned_part(df, group_cols, debt_col):
    # Count 0% and 100% occurences
    ser_pt2 = df[df[debt_col].isin((0, 1))]\
            .groupby(group_cols)[debt_col].value_counts()
    # include zero counts
    ser_pt2 = ser_pt2.reindex(pd.MultiIndex.from_product(
        ser_pt2.index.levels, names=ser_pt2.index.names),
                              fill_value=0)
    ser_pt2.index.set_names('bin', level=3, inplace=True)

    # ser_counts has the counts for normalization.
    ser_counts = df.groupby(group_cols)[debt_col].count()
    ser_pt2 = ser_pt2 / ser_counts

    return ser_pt2


def _rename_bins(ser_out, group_cols, debt_col):
    bin_names = []
    bin_name_dict = {
        '0.0': '0%',
        '(0.0, 0.25]': ']0, 25]%',
        '(0.25, 0.5]': ']25, 50]%',
        '(0.5, 0.75]': ']50, 75]%',
        '(0.75, 1.0]': ']75, 100]%',
        '1.0': '100%',
        '(1.0, inf]': '>100%',
    }
    bin_order = list(bin_name_dict.values())
    for val in ser_out.index.levels[3].values:
        bin_names.append(bin_name_dict.get(val.__str__(), val.__str__()))

    bin_categories = pd.Categorical(bin_names,
                                    categories=bin_order,
                                    ordered=True)
    ser_out.index.set_levels(bin_categories, level=3, inplace=True)

    # For some reason, .sort_index() does not sort correcly
    # -> Make it a dataframe and sort there.
    df_out = ser_out.reset_index()
    df_out['bin'] = pd.Categorical(df_out['bin'].values,
                                   bin_order,
                                   ordered=True)
    df_out = df_out.sort_values(group_cols + ['bin']).set_index(group_cols +
                                                                ['bin'])

    df_out.rename(columns={debt_col: 'in_bin'}, inplace=True)
    df_out['in_bin'] = (df_out['in_bin'] * 100).round(2)

    return df_out


def get_results(df):
    group_cols = ['City', 'Card', 'Colateral']
    debt_col = '% Debt Paid'

    ser_pt1 = _get_binned_part(df, group_cols, debt_col)
    ser_pt2 = _get_non_binned_part(df, group_cols, debt_col)
    ser_out = pd.concat([ser_pt1, ser_pt2])
    df_out = _rename_bins(ser_out, group_cols, debt_col)

    return df_out

df_out = get_results(df)

示例输出

In [1]: df_out
Out[1]:
                                         in_bin
City   Card        Colateral bin
Lisbon Master Card No        0%            0.00
                             ]0, 25]%    100.00
                             ]25, 50]%     0.00
                             ]50, 75]%     0.00
                             ]75, 100]%    0.00
                             100%          0.00
                             >100%         0.00
                   Yes       0%            0.00
                             ]0, 25]%      0.00
                             ]25, 50]%   100.00
                             ]50, 75]%     0.00
                             ]75, 100]%    0.00
                             100%          0.00
                             >100%         0.00
       Visa        No        0%            0.00
                             ]0, 25]%      0.00
                             ]25, 50]%    66.67
                             ]50, 75]%     0.00
                             ]75, 100]%   33.33
                             100%         33.33
                             >100%         0.00
                   Yes       0%           33.33
                             ]0, 25]%     33.33
                             ]25, 50]%     0.00
                             ]50, 75]%     0.00
                             ]75, 100]%   33.33
                             100%         33.33
                             >100%         0.00
Tokyo  Master Card No        0%           25.00
                             ]0, 25]%     25.00
                             ]25, 50]%    25.00
                             ]50, 75]%    25.00
                             ]75, 100]%    0.00
                             100%          0.00
                             >100%         0.00
                   Yes       0%            0.00
                             ]0, 25]%      0.00
                             ]25, 50]%    50.00
                             ]50, 75]%    50.00
                             ]75, 100]%    0.00
                             100%          0.00
                             >100%         0.00
       Visa        No        0%           75.00
                             ]0, 25]%     25.00
                             ]25, 50]%     0.00
                             ]50, 75]%     0.00
                             ]75, 100]%    0.00
                             100%          0.00
                             >100%         0.00
                   Yes       0%            0.00
                             ]0, 25]%     50.00
                             ]25, 50]%     0.00
                             ]50, 75]%     0.00
                             ]75, 100]%   50.00
                             100%          0.00
                             >100%         0.00

附录

所需的示例输出:“里斯本,签证,否”

有了这个组合

In [1]: df.loc[ (df['City'] == 'Lisbon') & (df['Card'] == 'Visa') & (df['Colateral'] == 'No')]
Out[1]:
      City  Card Colateral  Client Number  % Debt Paid
6   Lisbon  Visa        No              7          0.4
9   Lisbon  Visa        No             10          1.0
20  Lisbon  Visa        No             21          0.5

输出数据表应该有

0%            0%
]0, 25]%      0%
]25, 50]%     66.7%
]50, 75]%     0%
]75, 100]%    33.3%
100%          33.3%
>100%         0%

请注意,一对相交的 bin 对(]75, 100][100, 100])会导致输出列的总和有时大于 100%。

【讨论】:

【解决方案2】:

你可以做 value_counts

newdf=df.groupby(['City','Card','Colateral'])['% Debt Paid'].\
           value_counts(bins=[-0.1,0,0.25,0.5,0.75,1,1.0001,999],normalize=True)

【讨论】:

  • 这很好,但只能部分解决,我需要将 =1 与 >1 分开
  • 是否有可能在没有任何信息时在分支上显示 0?
  • 这个答案有几个错误:(1)案例(“里斯本”,“签证”,“否”)。应该有]25, 50]: 66.7%]75, 100]: 33.3%100%: 33.3%。这个答案将给出100%: 0%。 (主要bug),(2)支付100.005%会被计入100%,但应该算入&gt;100%bin(3)如果有-0.05的数据,会被计入@ 987654330@斌。 (4)如果有超过99900%的东西,将不计入任何bin。
猜你喜欢
  • 2020-12-30
  • 2018-07-01
  • 1970-01-01
  • 2020-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-05
  • 2020-04-16
相关资源
最近更新 更多