在数据帧中的列乘法中重构一个糟糕的“代码气味”

时间:2015-02-17 13:51:04

标签: python pandas

我还有另一个关于效率的问题。 我有以下类型的乘法:

df['Allocated'] = df['Base Days'] * df['Base (MW) Allocated'] * 24
df['Bought'] = df['Base Days'] * df['Base (MW) Bought'] * 24
df['Sold'] = df['Base Days'] * df['Base (MW) Bought'] * 24
df['Remaining'] = df['Base Days'] * df['Base (MW) Remaining'] * 24

我正在考虑使用for循环 - 但是有人在打字方面有更有效的方式。或者这是最有效的方式。 我可以定义一个函数

  def multiply(df['X']): 
      return df['X'] * df['Base Days'] * 24

这可能会使代码更具可重用性。有没有人有任何其他想法, 它只是感觉我在做一个糟糕的代码味道 - 我想要一些关于如何改进它的建议。

1 个答案:

答案 0 :(得分:2)

对于这类事情,没有固有的“坏代码味道”。例如,假设将来其中一个列,比如'Base (MW) Bought'需要区别对待其他列。在这种情况下,实际上每个不同的乘法步骤都是明确处理的,而不是隐式地在函数调用中或作为循环的迭代来处理。

但是,我可以理解,似乎有浪费的字符,因为您正在重复*24的逻辑和对'Base Days'的访问。

我可能会做的是为将来的扩展创建一些选项或以后增加灵活性的方法,但仍然或多或少地使用您的multiply想法:

def baseMultiply(data, colToMul, baseCol='Base Days', convFactor=24, preTreat=None):
    col_data = data[colToMult]
    if preTreat is not None:
        col_data = preTreat(col_data)
    return data[baseCol] * col_data * convFactor

然后在循环中执行乘法和赋值:

col_prefix = "Base (MW) "
for col in ['Allocated', 'Bought', 'Sold', 'Remaining']:
    df[col] = baseMultiply(df, col_prefix + col)

然后说稍后你要删除异常值,但仅在你报告'Bought'的数字的情况下(这是不现实的,但这只是一个例子)。您可以编写一个辅助函数,如:

def removeOutlier(lowerBound, upperBound, colData):
    return colData.clip(lowerBound, upperBound)

我们可以使用functools.partial将一些参数绑定到这个人身上。

import functools
boughtColClipper = functools.partial(removeOutlier, 5, 105)

现在我们可以修改上面的循环以检查我们何时在'Bought'列,并在这种情况下将此函数作为关键字参数preTreat

col_prefix = "Base (MW) "
for col in ['Allocated', 'Bought', 'Sold', 'Remaining']:
    treatment = None if col != 'Bought' else boughtColClipper
    df[col] = baseMultiply(df, col_prefix + col, preTreat=treatment)

现在它至少在某种程度上可以扩展,允许以后的数据清理,异常值限制,winsorization,变量z-scoring等等,这些都是以后出现的常见事情,需要痛苦地返回并破坏早期的代码。

我经常使用的最后一个技巧是名称映射。不像我上面那样迭代list列名称(隐含地假定赋值名称相同或直接从现有名称派生),您可以给出一个dict映射。

例如,在原始帖子中,您分配了新名称“已售出”,但在右侧,它是根据“Base(MW)Bought”和<​​strong> not 从“基地(MW)已售出”。我认为这是一个错字,因此我在我的代码中使用了“Base(MW)Sold”。

但是我们假设这不是一个错字,两个不同的“输出名称”(“买”和“卖”)来自一个输入名称(“Base(MW)Bought”)。

namesToAssign = {"Allocated":"Allocated", 
                 "Bought":"Bought", 
                 "Sold":"Bought", 
                 "Remaining":"Remaining"}

col_prefix = "Base (MW) "
for newCol, oldCol in namesToAssign.iteritems():
    treatment = None if newCol != 'Bought' else boughtColClipper
    df[newCol] = baseMultiply(df, col_prefix + oldCol, preTreat=treatment)

您甚至可以更进一步,从现有列映射到两者新输出列以及预处理功能,例如:

namesAndTreatments = {"Allocated":("Allocated", None), 
                      "Bought":("Bought", boughtColClipper),
                      "Sold":("Bought", None), 
                      "Remaining":("Remaining", None)}

col_prefix = "Base (MW) "
for newCol, (oldCol, treatment) in namesToAssign.iteritems():
    df[newCol] = baseMultiply(df, col_prefix + oldCol, preTreat=treatment)

甚至可以进一步扩展,以便namesAndTreatments内的每个值都包含额外的参数,记录处理程序,如果数据不好等后备数据的数据库连接等等。那时,你我希望重构任何namesAndTreatments是它自己的某种类,并通过解压缩该类的成员数据属性来使baseMultiply之类的函数工作(它将有助于分区和测试,而dict只是在其责任中成长和增长将难以维持。)