【问题标题】:Pandas excel difference generatorPandas excel差异生成器
【发布时间】:2018-02-21 14:32:15
【问题描述】:

我正在尝试创建一个 python 程序,它可以为我提供 2 个带有多个工作表的大型 excel 文件之间的差异。我得到它来将结果打印到 Excel,但显然当其中一个单元格包含日期时间数据时,将布尔数据框与包含日期的数据框相乘的操作不再起作用。我收到以下错误:

TypeError: *: 'bool' 和 'datetime.datetime' 的操作数类型不受支持

'EDIT':我刚刚意识到这个方法也不适用于字符串(它只适用于纯数字数据)。有什么更好的方法可以处理字符串、数字和时间数据?

#start of program
    import pandas as pd
    from pandas import ExcelWriter
    import numpy as np

    df1 = pd.read_excel('4_Input EfE_2030.xlsm',None)
    df2 = pd.read_excel('5_Input EfE_2030.xlsm',None)
    keys1=df1.keys()
    keys2=df2.keys()
    writer = ExcelWriter('test1.xlsx')
#loop for all sheets and create new dataframes with the differences    
    for x in keys1:
        df3 = pd.read_excel('4_Input EfE_2030.xlsm',sheetname=x,header=None)
        df4 = pd.read_excel('5_Input EfE_2030.xlsm',sheetname=x,header=None)
        dif = df3 != df4
        df=dif*df3
        df2=dif*df4
        nrcolumns=len(df.columns)
#when there are no differences in the entire sheet the dataframe will be empty. Add 1 to row indexes so the number coincides with excel rownumbers
        if not df.empty:
            # df.columns = ['A']
            df.index = np.arange(1, len(df) + 1)
        if not df2.empty:
            # df2.columns = ['A']
            df2.index = np.arange(1, len(df) + 1)
#delete rows with all 0
        df = df.loc[~(df == 0).all(axis=1)]
        df2 = df2.loc[~(df2 == 0).all(axis=1)]
#create new df with the data of the 2 sheets
        result = pd.concat([df,df2],axis=1)
        print(result)
        result.to_excel(writer,sheet_name=x)

【问题讨论】:

  • 我更新了我的答案以描述一种新方法,因为对您的原始问题进行了编辑(即,只有严格的数值适用于您的方法)。我希望它会有所帮助。它让你有一些地方,但不是全部。我只是没时间了! :)

标签: python excel pandas


【解决方案1】:

更新答案

方法

这是一个有趣的问题。另一种方法是使用 Pandas 提供的 Panel 数据结构将一个 Excel 工作表中的列值与另一个 Excel 工作表中的列值进行比较。此数据结构将数据存储为 3 维数组。使用存储在Panel 中的两个 Excel 工作表中的数据,我们可以比较工作表中由一个列或列组合(例如,唯一 ID)唯一标识的行。通过应用自定义函数来进行此比较,该函数将一个工作表中每一列的每个单元格中的值与第二个工作表中同一列的同一单元格中的值进行比较。这种方法的一个好处是每个值的数据类型不再重要,因为我们只是在比较值(例如,1 == 1'my name' == 'my name' 等)。

假设

这种方法对您的数据做了几个假设:

  1. 每个工作表中的行共享唯一标识每一行的一个或一组列。
  2. 用于比较的感兴趣的列存在于两个工作表中,并且共享相同的列标题。

(我可能没有注意到其他假设。)

实施

这种方法的实现有点复杂。此外,由于我无权访问您的数据,因此我无法专门针对您的数据自定义实现。话虽如此,我将使用如下所示的一些虚拟数据来实现这种方法。

“旧”数据集:

id  col_num col_str                col_datetime
 1  123     My string 1            2001-12-04
 2  234     My string 2            2001-12-05
 3  345     My string 3            2001-12-06

“新”数据集:

id  col_num col_str                col_datetime
 1  123     My string 1 MODIFIED   2001-12-04
 3  789     My string 3            2001-12-10
 4  456     My string 4            2001-12-07

请注意这两个数据帧的以下差异:

  1. id 1 的行中的col_str 不同
  2. col_num 在与id 3 的行中是不同的
  3. col_datetime 在与id 3 的行中是不同的
  4. id2 的行存在于“旧”而不是“新”中
  5. id 4 的行存在于“新”而不是“旧”中

好的,让我们开始吧。首先,我们将数据集读入单独的数据框:

df_old = pd.read_excel('old.xlsx', 'Sheet1', na_values=['NA'])
df_new = pd.read_excel('new.xlsx', 'Sheet1', na_values=['NA'])

然后我们为每个数据框添加一个新版本列,以保持我们的思路清晰。稍后我们还将使用此列将行从“旧”和“新”数据帧中分离到各自独立的数据帧中:

df_old['VER'] = 'OLD'
df_new['VER'] = 'NEW'

然后我们将“旧”和“新”数据集连接到一个数据帧中。请注意,ignore_index 参数设置为 True,以便我们忽略索引,因为它对于此操作没有意义:

df_full = pd.concat([df_old, df_new], ignore_index=True)

现在我们要识别两个数据帧中存在的所有重复行。这些行在“旧”和“新”数据帧中的所有列值都相同。换句话说,这些是不存在差异的行:

一旦确定,我们就会删除这些重复的行。我们剩下的是(a)两个数据帧之间不同的行,(b)存在于“旧”数据帧中但不存在于“新”数据帧中,以及(c)存在于“新”数据帧中但不是“旧”数据框:

df_diff = df_full.drop_duplicates(subset=['id', 'col_num', 'col_str', 'col_datetime'])

接下来,我们为“旧”和“新”数据帧中存在的行识别并提取 id 的值(即跨“旧”和“新”数据帧的主键)。请务必注意,这些ids包括存在于一个或其他数据帧中但不同时存在于两者中的行(即删除的行或添加的行):

diff_ids = df_diff.set_index('id').index.get_duplicates()

现在我们将df_full 限制为仅由diff_ids 中的ids 标识的那些行:

df_diff_ids = df_full[df_full['id'].isin(diff_ids)]

现在我们将“旧”和“新”数据帧中的重复行移动到单独的数据帧中,我们可以将它们插入到 Panel 数据结构中进行比较:

df_diff_old = df_diff_ids[df_diff_ids['VER'] == 'OLD']
df_diff_new = df_diff_ids[df_diff_ids['VER'] == 'NEW']

接下来,我们将这两个数据帧的索引设置为主键(即id)。这是Panel 有效工作所必需的:

df_diff_old.set_index('id', inplace=True)
df_diff_new.set_index('id', inplace=True)

我们将这两个数据帧放入Panel 数据结构中:

df_panel = pd.Panel(dict(df1=df_diff_old, df2=df_diff_new))

最后我们使用自定义函数 (find_diff) 和 apply 方法进行比较:

def find_diff(x):
    return x[0] if x[0] == x[1] else '{} -> {}'.format(*x)

df_diff = df_panel.apply(find_diff, axis=0)

如果您打印出df_diff 的内容,您可以很容易地注意到“旧”和“新”数据帧之间的哪些值发生了变化:

    col_num     col_str                             col_datetime
id              
1   123         My string 1 -> My string 1 MODIFIED 2001-12-04 00:00:00 
3   345 -> 789  My string 3                         2001-12-06 00:00:00 -> 2001-12-10 00:00:00

改进

我将留给您对此实现进行一些改进。

  1. 添加一个二进制 (1/0) 标志,指示是否存在一个或多个值 行已更改
  2. 确定删除了“旧”数据框中的哪些行 (即,不存在于“新”数据框中)
  3. 确定 添加了“新”数据框(即,不存在于“旧”数据框中)

原答案

问题:

问题是您无法对datetimes 执行算术运算。

但是,您可以对 timedeltas 执行算术运算。

我能想到一些可能对您有所帮助的解决方案:

解决方案一:

将您的datetimes 转换为字符串。

如果我对您的问题的理解正确,那么您是在比较 Excel 工作表的差异,对吗?如果是这种情况,那么我认为 datetimes 是否表示为显式 datetimes 并不重要(即,您没有执行任何 datetime 计算)。

要实施此解决方案,您需要将 pd.read_excel()' calls and explicitly set thedtypesparameter to convert yourdatetimes` 修改为字符串:

df1 = pd.read_excel('4_Input EfE_2030.xlsm', dtypes={'LABEL FOR DATETIME COL 1': str})

解决方案 2:

将您的datetimes 转换为timedeltas

对于每个datetime 列,您可以使用:pd.Timedelta(df['LABEL FOR DATETIME COL'])

总的来说,在没有看到您的数据的情况下,我相信解决方案 1 是最直接的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-08
    • 2021-11-26
    • 1970-01-01
    • 2020-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多