更改单行值时保持总和约束行的比例

Posted

技术标签:

【中文标题】更改单行值时保持总和约束行的比例【英文标题】:Maintaining proportionality of sum constrained row when changing a single row value 【发布时间】:2019-12-18 07:12:51 【问题描述】:

我有一个由成分数据组成的数据集。 每列代表混合物整体中某个组分的百分比(十进制值)。 每行总和为 1。

如果混合物中的一种成分发生变化,其余成分必须相应变化以满足总和约束。

我正在使用这些数据执行多元线性回归,它需要进行一些转换,以使回归系数有意义且可解释。数据集包含零值,这是我尝试实施的特定类型转换的问题。

在我可以执行此转换之前,建议的操作是用一个小数字替换所有零值,并调整剩余的组件以使总和约束仍然得到满足。

您可以在下面的 dummy df 中看到有连续多个零值的情况。

data = 'X1': [0.21, 0.08, 0.57, 0.03],
        'X2': [0.27, 0.56, 0.0, 0.02],
        'X3': [0.0, 0.14, 0.0, 0.45],
        'X4': [0.13, 0.02, 0.26, 0.37],
        'X5': [0.39, 0.2, 0.17, 0.13]

df = pd.DataFrame(data)

print(df)

我们只考虑一行,所以这样做的公式如下:

设原始值为r_i。对于 delta_i 的组件 r_i 的更改,我们得到新值 x_i

所以,x_i = r_i + delta_i

为了保持其余成分之间的相对比例,

r_j为剩余分量的原始值,

那么第 j 个分量 x_j 的新值是,

x_j = r_j - ((r_j / (1 - r_i) * delta_i) and i != j

我正在努力为这个问题编写一个适当的循环,它将在数据集中搜索零值,然后在索引和列中添加一个小数字 包含零值,然后继续使用我上面描述的公式调整整行。

编辑:

很抱歉数学公式的表述不好。

对于虚拟 df 中的第一行,公式的应用很简单,因为该行中只有一个零:

重要的是其余组件之间的相对比例保持不变,您可以看到这里我将零值更新为一个小数字。

对于虚拟 df 中的第三行,事情变得有点复杂。我通过添加一个小数来更新第一个 (X2) 零值。第二个 (X3) 零值保持为零,因为公式正在乘以零。 所以我进行了第二次更新,使得 X2 和 X3 现在是小的非零值,这显示在下表的第三行。

对于行上存在多个零的情况,保持其余组件之间的相对比例也是相同的情况。

我想不出第一个问题的循环,更不用说第二个了! 另外,不用担心在相对比例表中除以一个小数会产生很大的数字,我稍后会处理。

【问题讨论】:

基于上述数据框的实际结果示例可以使这变得更好。您可以在 Excel 中做一个前后对比,截屏,然后添加到您的帖子中。 啊哈!编辑后,这使我的答案过时了。暂时删除并编辑。我想我现在明白你的意思了。 您想通过降低幅度来保持元素分布吗? 已编辑,现在显示的结果与您的示例相同。 【参考方案1】:

以下是编辑后的答案:

import pandas as pd
# To show 10 decimal points.
pd.options.display.float_format = ':.10f'.format

data = 'X1': [0.21, 0.08, 0.57, 0.03],
        'X2': [0.27, 0.56, 0.0, 0.02],
        'X3': [0.0, 0.14, 0.0, 0.45],
        'X4': [0.13, 0.02, 0.26, 0.37],
        'X5': [0.39, 0.2, 0.17, 0.13]

df = pd.DataFrame(data)

delta_i = 0.000001
r_i = 0.0

# Provided formula.
def adjust_proportion(r_j, r_i, delta_i):
    return r_j - ((r_j / (1 - r_i)) * delta_i)

# For row-wise application.
def adjust_row(row, r_i, delta_i):

    # Get all zeros and their count in the row.
    zero_mask = (row == 0)
    zero_count = row[zero_mask].shape[0] # Get only x.

    # For every zero, adjust proportions for "cells" not in mask.
    for i in range(zero_count):
        row[~zero_mask] = row[~zero_mask].apply(lambda x: adjust_proportion(x, r_i, delta_i))

    # Increase the mask by delta_i across the board.
    row[zero_mask] += delta_i

    return row

# Apply ROW-WISE using axis=1.
df.apply(lambda x: adjust_row(x, r_i, delta_i), axis=1)
print(df)

# Check sums.
print(df.apply(lambda x: x.sum(), axis=1))

这给出了以下结果:

还有更优化的方法,但这应该考虑到一般逻辑。

【讨论】:

我现在试试这个。 杰罗姆,你是某种巫师!在我编辑帖子的时间里,你解决了我的问题。确认使用我的完整数据集并保留所有其他组件之间的相对比例。谢谢! 不客气。我使代码比简洁更具可读性,如果你注意到的话,我还加入了一些其他概念,这样它就很灵活了。下线您可能想要定位特定值,因此r_idelta_i 是可变的。您现在还知道apply,它只是一种将函数按行或按列(过于简化的定义、警告、大声笑)应用于数据帧的方法。希望它运作良好。我也使用机器学习,特征工程很痛苦。 :) 我非常感谢您编写代码的可读性。我知道apply 是要走的路,但我一生都想不出如何让它发挥作用。我最终避免了它,并尝试将嵌套在循环中的函数嵌套在循环中等等。我刚刚开始我的 ML 之旅,希望能有一段时间跨越道路。【参考方案2】:

你可以使用:

def exclude_zero(e, delta_i):
    """Replace zeros with a delta_i value by keeping the other non zeros value in the same distribution and total sum to 1"""

    zero_count = e.count(0)
    extra_amount = zero_count * delta_i

    for index, value in enumerate(e):
        if value == 0 :
            e[index] = delta_i
        else:
            e[index] = value * (1 - extra_amount)

    return e

data = 'X1': [0.21, 0.08, 0.57, 0.03],
        'X2': [0.27, 0.56, 0.0, 0.02],
        'X3': [0.0, 0.14, 0.0, 0.45],
        'X4': [0.13, 0.02, 0.26, 0.37],
        'X5': [0.39, 0.2, 0.17, 0.13]

df = pd.DataFrame(data)

for index in range(len(df)):
    df.iloc[index] = exclude_zero(df.iloc[index].values.tolist(), 0.000001)

pd.options.display.precision = 8

df

【讨论】:

@DariusNicholson 立即查看 您好 rusu_ro1,感谢您的努力。对不起,我花了这么长时间才回复你。在我运行 Jerome 的代码后,我出去了。你的代码也很好用!保持相对比例。不过,我注意到的一件事是,在您的代码和 Jerome 的代码之间,一旦达到小数点后 10 位,数字就会开始不同。当然,这是微不足道的。总而言之,这是一个非常干净的问题解决方案。谢谢!

以上是关于更改单行值时保持总和约束行的比例的主要内容,如果未能解决你的问题,请参考以下文章

在更新值时临时更改显示值的 Excel 单元格

更改值时将颜色条标签保持在旧位置

如何更改所选 SlidingMenu 行的背景颜色并保持不变,直到选择其他行?

以编程方式更改纵横比约束值

屏幕尺寸更改时保持图形轮廓

如何更改自动布局约束