【问题标题】:Choosing between pandas, OOP classes, and dicts (Python)在 pandas、OOP 类和 dicts 之间进行选择(Python)
【发布时间】:2017-02-23 19:23:37
【问题描述】:

我编写了一个程序来读取几个 .csv 文件(它们不大,每个都有几千行),我做了一些数据清理和整理,这是每个 .csv 文件看起来的最终结构(假数据仅用于说明目的)。

import pandas as pd
data = [[112233, 'Rob', 99], [445566, 'John', 88]]
managers = pd.DataFrame(data)
managers.columns = ['ManagerId', 'ManagerName', 'ShopId']
print managers

   ManagerId ManagerName  ShopId
0     112233         Rob      99
1     445566        John      88


data = [[99, 'Shop1'], [88, 'Shop2']]
shops = pd.DataFrame(data)
shops.columns = ['ShopId', 'ShopName']
print shops

   ShopId ShopName
0      99    Shop1
1      88    Shop2

data = [[99, 2000, 3000, 4000], [88, 2500, 3500, 4500]]
sales = pd.DataFrame(data)
sales.columns = ['ShopId', 'Year2010', 'Year2011', 'Year2012']
print sales

   ShopId  Year2010  Year2011  Year2012
0      99      2000      3000      4000
1      88      2500      3500      4500

然后我使用xlsxwriterreportlab Python 包在迭代数据框时创建自定义 Excel 工作表和 .pdf 报告。一切看起来都很棒,而且所有命名的包都做得很好。

但我担心的是,我觉得我的代码很难维护,因为我需要在多次调用中多次访问相同的数据框行。

假设我需要获取负责 2010 年销售额超过 1500 的商店的经理姓名。我的代码中充满了这种调用:

managers[managers['ShopId'].isin(
    sales[sales['Year2010'] > 1500]['ShopId'])]['ManagerName'].values
>>> array(['Rob', 'John'], dtype=object)

我认为在阅读这行代码时很难看到发生了什么。我可以创建多个中间变量,但这会添加多行代码。

牺牲数据库规范化思想并将所有部分合并到一个数据帧中以获得更易于维护的代码有多常见?使用单个数据帧显然有缺点,因为在尝试合并以后可能需要的其他数据帧时可能会变得混乱。合并它们当然会导致数据冗余,因为可以将同一个经理分配给多个商店。

df = managers.merge(sales, how='left', on='ShopId').
    merge(shops, how='left', on='ShopId')
print df

   ManagerId ManagerName  ShopId  Year2010  Year2011  Year2012 ShopName
0     112233         Rob      99      2000      3000      4000    Shop1
1     445566        John      88      2500      3500      4500    Shop2

至少这个调用变小了:

df[df['Year2010'] > 1500]['ManagerName'].values
>>> array(['Rob', 'John'], dtype=object)

也许 pandas 不适合这种工作?

办公室的 C# 开发人员对我皱眉并告诉我使用这些类,但随后我将拥有一堆方法,例如 get_manager_sales(managerid) 等等。为报告迭代类实例听起来也很麻烦,因为我需要实现一些排序和索引(我通过pandas 免费获得)。

字典可以工作,但它也使得修改现有数据、进行合并等变得困难。语法也没有变得更好。

data_dict = df.to_dict('records')
[{'ManagerId': 112233L,
  'ManagerName': 'Rob',
  'ShopId': 99L,
  'ShopName': 'Shop1',
  'Year2010': 2000L,
  'Year2011': 3000L,
  'Year2012': 4000L},
 {'ManagerId': 445566L,
  'ManagerName': 'John',
  'ShopId': 88L,
  'ShopName': 'Shop2',
  'Year2010': 2500L,
  'Year2011': 3500L,
  'Year2012': 4500L}]

获取负责 2010 年销售额超过 1500 的商店的经理姓名。

[row['ManagerName'] for row in data_dict if row['Year2010'] > 1500]
>>> ['Rob', 'John']

在我操作的数据的这种特殊情况下,我应该一直使用pandas,还是有其他方法可以在利用pandas 的强大功能的同时编写更简洁的代码?

【问题讨论】:

    标签: python class oop pandas dictionary


    【解决方案1】:

    我会选择 Pandas,因为它速度更快,具有出色且极其丰富的 API,源代码看起来更干净更好,等等。

    顺便说一句,下面这行很容易改写:

    managers[managers['ShopId'].isin(sales[sales['Year2010'] > 1500]['ShopId'])]['ManagerName'].values
    

    作为:

    ShopIds = sales.ix[sales['Year2010'] > 1500, 'ShopId']
    managers.query('ShopId in @ShopIds')['ManagerName'].values
    

    IMO 很容易阅读和理解

    PS 您可能还希望将数据存储在 SQL-able 数据库中并使用 SQL 或将其存储在 HDF 存储中并使用 where 参数 - 在这两种情况下,您都可以从索引“搜索”列中受益

    【讨论】:

    • 酷,非常感谢您的回答,让我们确信选择 pandas 来编写报告程序是正确的做法。您是否还考虑将所有 csv 文件数据帧合并为一个,以避免一直交叉引用多个帧?
    • @AlexTereshenkov,当然你可以尝试去规范化你的表并将所有内容放在一个平面 DF 中,但要注意possible pitfalls
    【解决方案2】:

    创建对数据框进行操作的类不是一个好主意,因为它会隐藏您正在使用数据框的事实,并为非常糟糕的决策开辟道路(例如使用 @987654321 迭代数据框@循环)。

    解决方案 1:对数据进行非规范化。 您不必以正常形式保存数据。当您必须在整个数据库中保持条目一致时,最好使用普通形式。这不是数据库,您不会进行不断的插入、更新和删除。因此,只需对其进行非规范化,并使用一个大型数据框,因为它显然更方便,并且更适合您的需求。

    解决方案 2:使用数据库。 您可以将数据转储到 SQLite 数据库中(pandas 有一个内置函数),并在其上执行各种疯狂的查询。在我个人看来,SQL 查询比您发布的内容更具可读性。 如果你定期做这种分析,并且数据结构保持不变,这可能是一个更可取的解决方案。您可以将数据转储到数据库中,然后使用 SQLAlchemy 进行处理。

    解决方案 3. 创建您自己的数据帧。 您可以从pandas.DataFrame 继承并向其添加自定义方法。不过,您需要深入了解pandas 的内容,以了解如何实现这些方法。例如,您可以通过这种方式创建访问数据框某些部分的自定义方法。

    除非您非常了解 pandas,否则我会选择解决方案 1 或 2。如果您需要更大的灵活性,并且每次数据操作都不同,请使用 1。如果您需要每次执行大致相同的分析,使用 2(特别是如果您的数据分析代码是更大应用程序的一部分)。

    另外,我不明白为什么“添加更多代码行”不好。通过将一个巨大的单行分解为许多表达式,您不会增加 实际 复杂性,而是会降低 感知 复杂性。也许您需要做的只是重构您的代码,并将一些操作打包成可重用的函数?

    【讨论】:

    • 感谢您的反馈,非常有帮助。 1. 说到类,我指的不是基于 pandas 的类,而是可以从 csv 文件构建的类(如 csv dictreader)。 2. pack some operations into reusable functions? 是我也想过的,也许添加一些像 get_manager_shops()get_shops_totalsale() 这样的函数并将它们放入一个单独的模块中可以让我的生活更轻松(但在它们内部,它仍然会调用数据帧,对吧?)
    • 是的,您只需将这些电话隐藏起来。还有一个问题是pandas 中的许多操作通常会返回数据帧上的视图。这在调用函数get_shops_totalsale(df) 时并不明显。因此,您必须牢记此类内容,这将使您的代码难以理解。如果您忘记了这一点,您可能会得到“神奇”的意想不到的副作用。
    • 您能否详细说明“非规范化”数据的含义?
    猜你喜欢
    • 1970-01-01
    • 2021-12-06
    • 2014-10-31
    • 2014-08-03
    • 1970-01-01
    • 1970-01-01
    • 2011-01-29
    • 2010-12-06
    • 2012-04-13
    相关资源
    最近更新 更多