我写了一个程序来读取几个。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
然后我使用xlsxwriter
和reportlab
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)
也许熊猫不适合做这种工作?c#开发人员在办公室对我皱眉头,告诉我使用类,但我将有一堆方法,如get_manager_sales(managerid)
等。迭代类实例进行报告听起来也很麻烦,因为我需要实现一些排序和索引(我在pandas
中免费获得)。
Dictionary可以工作,但它也使修改现有数据、合并等变得困难。语法也没有变得更好。
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
的功能?
我会选择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
在我看来,这很容易阅读和理解
PS你可能也想将你的数据存储在一个SQL
支持的数据库中并使用SQL,或者将它存储在HDF store中并使用where
参数——在这两种情况下,你都可以从索引"搜索"列中受益
创建对数据帧进行操作的类并不是一个好主意,因为它会隐藏您正在使用数据帧的事实,并为非常糟糕的决策打开了道路(例如使用for
循环在数据帧上迭代)。
解决方案1:对数据进行反规范化。您不必以标准形式保存数据。当您必须在整个数据库中保持条目的一致性时,最好使用标准格式。这不是一个数据库,你不能不断地插入、更新和删除。因此,只需将其非规范化,并使用一个大型数据框架,因为它显然更方便,更适合您的需求。
方案2:使用数据库。您可以将数据转储到SQLite数据库中(pandas有一个内置函数),并对其执行各种疯狂的查询。在我个人的选择中,SQL查询比你发布的东西更具可读性。如果您定期进行这种分析,并且数据结构保持不变,那么这可能是一个更好的解决方案。您可以将数据转储到数据库中,然后使用SQLAlchemy处理它。
解决方案3。创建您自己的数据框。您可以继承pandas.DataFrame
并向其添加自定义方法。但是,您需要深入了解pandas
的核心,以了解如何实现这些方法。例如,通过这种方式,您可以创建自定义方法来访问数据框架的某些部分。
除非你非常了解熊猫,否则我会选择解决方案1或2。如果您需要更大的灵活性,并且每次的数据操作都不同,请使用1。如果您需要每次执行大致相同的分析,请使用2(特别是如果您的数据分析代码是更大应用程序的一部分)。
而且,我不明白为什么"增加更多的代码行"是不好的。通过将一个巨大的单行代码分解成许多表达式,您不会增加实际的复杂性,而会降低感知到的复杂性。也许您所需要做的只是重构代码,并将一些操作打包成可重用的函数?