更改单行值时保持总和约束行的比例
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_i
和delta_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 位,数字就会开始不同。当然,这是微不足道的。总而言之,这是一个非常干净的问题解决方案。谢谢!以上是关于更改单行值时保持总和约束行的比例的主要内容,如果未能解决你的问题,请参考以下文章