使用exec使用动态逻辑修改熊猫数据框

时间:2019-05-03 17:27:19

标签: python pandas

假设我有一个脚本,该脚本从数据库中将数据读取到数据帧中,对该数据帧运行一些逻辑,然后将结果数据帧导出到另一个数据库表中,如下所示。问题是exec函数后, transform.py 中的数据框不会被覆盖。

注意:这是一个简单的示例,用于说明问题,而不是我尝试使用此方法解决的实际问题。

期望:

执行前

+---------+---------------+--------------+----------+
| metric  | modified_date | current_date | datediff |
+---------+---------------+--------------+----------+
| metric1 | 2019-03-31    | 2019-05-03   |       33 |
| metric2 | 2019-03-31    | 2019-05-03   |       33 |
| metric3 | 2019-03-31    | 2019-05-03   |       33 |
| metric4 | 2019-03-20    | 2019-05-03   |       44 |
+---------+---------------+--------------+----------+

执行后

+---------+---------------+--------------+----------+
| metric  | modified_date | current_date | datediff |
+---------+---------------+--------------+----------+
| metric4 | 2019-03-20    | 2019-05-03   |       44 |
+---------+---------------+--------------+----------+

实际:

执行前

+---------+---------------+--------------+----------+
| metric  | modified_date | current_date | datediff |
+---------+---------------+--------------+----------+
| metric1 | 2019-03-31    | 2019-05-03   |       33 |
| metric2 | 2019-03-31    | 2019-05-03   |       33 |
| metric3 | 2019-03-31    | 2019-05-03   |       33 |
| metric4 | 2019-03-20    | 2019-05-03   |       44 |
+---------+---------------+--------------+----------+

执行后

+---------+---------------+--------------+----------+
| metric  | modified_date | current_date | datediff |
+---------+---------------+--------------+----------+
| metric1 | 2019-03-31    | 2019-05-03   |       33 |
| metric2 | 2019-03-31    | 2019-05-03   |       33 |
| metric3 | 2019-03-31    | 2019-05-03   |       33 |
| metric4 | 2019-03-20    | 2019-05-03   |       44 |
+---------+---------------+--------------+----------+

它们是相同的!

transform.py

def dataframe_transform(logic, source_table, dest_table, database, existing_rows='truncate'):
    ...
    df = table_to_df(table=source_table, database=database)

    try:
        exec(logic)
    except Exception:
        raise

    result = df_to_table(dataframe=df, database=database, table=dest_table, existing_rows=existing_rows)

    return result

逻辑过滤出数据框以查找需要更新的记录,并启动另一个过程,并使用新的过滤后的数据覆盖原始数据框。

logic.py

# This is just an example I made up - please don't focus on solving this.

late_df = pd.DataFrame()

# Check if data is late
late_cutoff = 40
for index, row in df.iterrows():
    if row['datediff'] >= late_cutoff:
        late_df = late_df.append(row, ignore_index=True)

... # Do something else

df = late_df # Save flagged records by updating the original dataframe.

我为什么要这样做?在这种情况下,我知道输入是安全的,它使我可以将此代码重用于各种脚本并分离出变换逻辑。

4 个答案:

答案 0 :(得分:5)

检查范围。从提供的代码中无法分辨,但我怀疑您的exec调用未正确管理范围(本地,全局)。 “在Python 3中,exec是一个函数;它的使用对使用它的函数的编译字节码没有影响。” (来自What's the difference between eval, exec, and compile?

另请参阅:https://www.programiz.com/python-programming/methods/built-in/exec

个人意见:eval / exec是邪恶的,应避免。

其他人在评论中表达的最后一点。该代码示例显示,您仍在行中思考,并在基于行的操作(对于itterrows中的x)中将向量(df ['col'])与标量(late_cutoff)混合使用这是熊猫用户的常见问题,我对于其他人,在此类问题上进行了大量重构。如果您可以按照设计的方式使用熊猫来更改代码以使用熊猫,那么程序的速度将提高一个数量级:无循环且不更改原始数据。一次读取-使用更改后的数据创建一个新的数据框,而无需使用迭代器-一次写入。如果必须循环,请创建一组键并遍历该键以创建矢量化操作:

keys = set(df['key_col'])
for key in keys:
    dfx = df[df[key > limit]]

这也可能对您有用(请参阅“多次写入逻辑以提高写入速度”)Bulk Insert A Pandas DataFrame Using SQLAlchemy

答案 1 :(得分:1)

这是关于python和数据库的混合问题。我认为您可能需要同时检查python和数据库。

假设您使用的是mysql。 (我注意到检查部分看起来像mysql客户端输出) 期望从DB的source_table读取到数据帧,然后通过ogic.py更改数据帧。将其写入dest_table。你期望 1.之前/之后应该不同。

我的问题: 1.源表之前/之后应该相同。是您的期望吗?是否有可能,检查零件错误:比较了源表之前/之后。正如我提到的一样。

    目标表之前/之后的
  1. 应该不同。如果期望不同,如何确保df_table进程按期望完成? 是否有可能在参数中传递了错误的目标表名称并更改了错误的表。但仍要检查目标名称

  2. 目标表之前/之后的
  3. 不变,并且返回的结果代码未显示它。

要解决此问题,我认为跟踪点或日志应该是有效的工具

祝你好运

答案 2 :(得分:1)

就像已经提到的每个人一样,您应该更喜欢简单地导入代码,但是我认为您正在以某种方式动态地构建“代码字符串”,所以这对您来说是不可能的。

确定要使用exec(logic)吗?在我测试的简单示例中,它工作正常。也许您正在使用exec(logic, globals(), locals())exec(logic, globals())?这种情况下exec使用“ locals / globals的副本”,因此它不会更新当前作用域的真实“ locals”。

logic = """
late_df = df.replace('a', 'x')
df = late_df
"""


def simple_exec_transform(df):
    df = df + 'opq'
    try:
        exec(logic)
    except Exception:
        raise

    return df


def bad_transform(df):
    df = df + 'opq'
    try:
        exec(logic, globals(), locals())
    except Exception:
        raise

    return df


def run_logic(locals_dict, return_variable='df'):
    exec(logic, globals(), locals_dict)
    if return_variable not in locals_dict:
        raise NameError("return_variable is not available in locals after exec(logic)")
    return locals_dict[return_variable]


def controlled_locals_exec_transform(df):
    df = df + 'opq'
    try:
        df = run_logic({'df': df})
    except Exception:
        raise
    return df


print(simple_exec_transform('abcdef'))
print(bad_transform('abcdef'))
print(controlled_locals_exec_transform('abcdef'))
# xbcdefopq
# abcdefopq
# xbcdefopq

答案 3 :(得分:0)

我知道它很容易将代码片段作为输入,方便您使用解释器。您可以根据需要选择在运行时导入不同的logic模块。

if predicate1:
    import logic_one as logic
elif predicate2:
    import logic_two as logic

logic.my_logic_operations_on_dataframe(df)