【问题标题】:Python: How best to parse csv and count values for only a subsetPython:如何最好地解析 csv 并仅计算一个子集的值
【发布时间】:2014-09-14 20:48:26
【问题描述】:

我有一个 CSV 文件,其中包含 3 列和 11 行的以下内容,第一行是标题。我自己创建了这个,以便有一个简单的文件可供学习。每个订单项都是一个水果订单。

OrderNo      Fruit     Origin
1           Apple        NY
2           Orange       FL      
3           Banana       CA
4           Pear         NJ
5           Grapes       VA
6           Grapes       VA
7           Grapes       MD
8           Grapes       MA
9           Pineapple    HI
10          Grapes       GA

我正在尝试在 Python 中解析这些数据,以执行以下操作:

(1) 确定为每种水果生成最多订单的状态,(2) 确定每种水果任何单个状态的最高订单数,(3) 按字母顺序输出此结果,如下所示:

Apple NY 1
Banana CA 1
Grapes VA 2
Orange FL 1
Pear NJ 1
Pineapple HI 1

使用 csv.reader 读取 csv 文件后,我尝试使用 Counter 和 for 循环完成计数:

import csv
from collections import Counter 

cnt = Counter()
f = open("/test.csv")
reader = csv.reader(f, delimiter=",")
header = next(f) 

for row in reader:   
    cnt[row[2]] += 1 

但是有更好的方法吗?

【问题讨论】:

  • 如果 2 个州对特定水果的订单数量相同,您想输出什么?如果某些水果没有订单,您是否愿意在输出中完全不包含该水果?
  • 刚刚发现一个错字,输出应该在 VA 之后显示 2,因为那里有 2 个订单,这是该水果的最大值。如果任何水果有 0 个订单,则没有输出。

标签: python csv


【解决方案1】:

我实际上会使用 pandas,它是列表/字典/电子表格/数据库的组合。它专为以这种方式处理数据而设计。

import pandas as pd
from collections import defaultdict

path_to_file = "/test.csv"
df = pd.read_csv(path_to_file)

groups = df.groupby(['Fruit', 'Origin'])
max_for_fruit = defaultdict(int) #first pass through the groups, store the maximum for each fruit to handle ties

for g in groups:
    fruit, count = g[0][0], len(g[1])
    max_for_fruit[ fruit ] = max( max_for_fruit[fruit], count )

for g in groups:
    fruit, state, count = g[0][0], g[0][1], len(g[1])
    if count == max_for_fruit[ fruit ]:
        print( "{} {} {}".format(fruit, state, count ) )

这是输出。

Apple NY 1
Banana CA 1
Grapes VA 2
Orange FL 1
Pear NJ 1
Pineapple HI 1

http://pandas.pydata.org/pandas-docs/stable/groupby.html

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.io.parsers.read_csv.html

http://pandas.pydata.org/pandas-docs/stable/tutorials.html

【讨论】:

  • 几个问题:(1) 什么代码或属性按字母顺序排序列表? (2) 无论如何要使用常规字典而不是 defaultdict(int) 来执行此操作?我正在尝试使用常规的 dict,但在最大和计数方面遇到了问题。
  • 排序是自动的,如果您不希望它们按原始顺序排序并保持原始顺序,可以将 'sort=False' 传递给 groupby。我使用 defaultdict 是因为我希望第一个比较为零(因为你不能吃负果,所以它是安全的)。您可以使用常规 dict,但随后您必须添加额外的逻辑来处理第一种情况,即您没有以前的水果值。 if fruit in fruit_dict: 从上面的代码做比较 else: fruit_dict[fruit] = len(g[1]) #initialize it with the first value)
【解决方案2】:

您的方法很好,并且效果很好,但是您需要两级嵌套(按水果和按状态计数。为了清晰起见,另一个小改进是使用命名元组:

import csv
from collections import Counter, namedtuple, defaultdict
from itertools import imap

reader = csv.reader(data)
Order = namedtuple('Order', next(reader))

state_orders = defaultdict(Counter)
for order in imap(Order._make, reader):
    state_orders[order.fruit][order.origin] += 1

for fruit, counts_by_state in sorted(state_orders.items()):
    state, cnt = counts_by_state.most_common(1)[0]
    print '%s is ordered most by %s with %s orders' % (fruit, state, cnt)

虽然 dicts 和 counters 可以轻松处理此类问题,但使用 sqlite3 模块可能会更好。 SQL 就是为了解决这些问题而诞生的:

import csv
import sqlite3

reader = csv.reader(data)
header = next(reader)

conn = sqlite3.connect(':memory:')
c = conn.cursor()
c.execute('CREATE TABLE Orders (order_no integer, fruit text, origin text)')
c.executemany('INSERT INTO Orders VALUES (?,?,?)', reader)
c.execute('''CREATE VIEW StateOrders AS
             SELECT fruit, origin, COUNT(*) as cnt
             FROM Orders GROUP BY fruit, origin ''')

for fruit, state, cnt in c.execute('''
    SELECT fruit, origin, cnt
    FROM StateOrders AS Outer
    WHERE cnt = (SELECT MAX(cnt) FROM StateOrders WHERE Outer.fruit = fruit)
    ORDER BY FRUIT '''):
        print '%s is ordered most by %s with %s orders' % (fruit, state, cnt)

【讨论】:

  • 我建议 pandas 对于这类事情要干净得多......请参阅我的解决方案。
  • 如果你有 Pandas,它就很棒。也就是说,一个人应该知道如何直接使用 Python。
  • 访问像Order._make这样的受保护成员的可接受程度如何?
  • 在命名元组中,所有方法都带有下划线前缀,以防止命名空间与用户定义的字段名称冲突。所以,这些方法不是私有的。
猜你喜欢
  • 2011-12-05
  • 2011-02-26
  • 2011-10-22
  • 2013-03-14
  • 2010-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多