【问题标题】:splitting a data frame by date and computing median for all rows with each date按日期拆分数据框并计算每个日期的所有行的中位数
【发布时间】:2018-05-22 06:39:39
【问题描述】:

我正在尝试粗略估计员工在给定月份所能完成的工作量。

我有一个大致像这样的 csv(虽然它要大得多):

+--------+-------+---------------+
|  Date  | Name  | Units of Work |
+--------+-------+---------------+
| 1/1/17 | Bob   |           450 |
| 2/1/17 | Alice |           300 |
| 2/1/17 | Bob   |           450 |
| 2/1/17 | Larry |            50 |
| 3/1/17 | Alice |           400 |
| 3/1/17 | Bob   |            11 |
| 3/1/17 | Larry |           100 |
| 4/1/17 | Alice |          1000 |
| 4/1/17 | Bob   |           240 |
| 4/1/17 | Larry |            33 |
+--------+-------+---------------+

我想:

  1. 计算每个“日期”的中值“工作单元”
  2. 确定是否有任何“姓名”在该“日期”内完成的“工作单位”中位数是否低于 20%
  3. 如果“姓名”的占比低于中位数的 20%,请删除它
  4. 将“日期”剩余的“姓名”计数乘以该“日期”的“工作单位”中位数
  5. 输出一个新的 csv,其中每个“日期”仅出现一次,各占一行,并且该日期的中位数“工作单元”乘以该“日期”的剩余“名称”

我什至无法满足要求 1,更不用说 2 到 5。我为每个日期获取一个文件。我得到一个名为“NewColumn”的新列,其中填充了“中位数”这个词,而不是具有中位数的列,如下所示:

# -*- coding: utf-8 -*-
import pandas as pd
df = pd.read_csv('source.csv')
df = df.sort_values('date_trunc').assign(NewColumn='median')
df.median(axis=None, skipna=None, level=None, numeric_only=None)
for i, g in df.groupby('date_trunc'):
    g.to_csv('{}.csv'.format(i), header=True, index_label=False, index=False)
    +---------+-------+---------------+-----------+
    |  Date   | Name  | Units of work | NewColumn |
    +---------+-------+---------------+-----------+
    | 12/1/16 | Alice |          6222 | median    |
    | 12/1/16 | Bob   |         14530 | median    |
    | 12/1/16 | Larry |         16887 | median    |
    +---------+-------+---------------+-----------+

我知道我在这里可能做错了很多,但我非常感谢一些指导。

我最终想要的是一个带有这个的 csv:

+---------+--------+
|  Date   | Median |
+---------+--------+
| 12/1/16 |   1110 |
| 1/1/17  |   1400 |
| 2/1/17  |   1200 |
+---------+--------+

【问题讨论】:

  • 你失去了我。您在第 1 步中有一个“给定”日期,然后在第 5 步中需要一个“不同”日期的 csv。什么。
  • 我正在寻找 2017 年 1 月所有计数的中位数、2017 年 2 月所有计数的中位数、2017 年 3 月所有计数的中位数等...我试图澄清 #1 和 #5

标签: python pandas csv date median


【解决方案1】:

我希望以下步骤能让您更接近所需的 CSV 输出。

首先,这是输入 DataFrame 的清晰再现,供其他希望复制粘贴到 pd.read_clipboard() 的人使用:

     Date     Name     Units of Work
0   Jan-17    Bob               450.0
1   Feb-17    Alice             300.0
2   Feb-17    Bob               450.0
3   Feb-17    Larry              50.0
4   Mar-17    Alice             400.0
5   Mar-17    Bob                11.0
6   Mar-17    Larry             100.0
7   Apr-17    Alice            1000.0
8   Apr-17    Bob               240.0
9   Apr-17    Larry              33.0

0。将日期转换为 python 日期时间(为了合理的排序顺序)

# Docs on Python datetime format strings: https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
df['Date'] = pd.to_datetime(df['Date'].apply(lambda x: x.strip()), format='%b-%y')

1。对于每个日期,找出工作单位的中位数

meds = df.groupby('Date')[['Units of Work']].median()
meds
    Units of Work
Date    
2017-01-01  450.0
2017-02-01  300.0
2017-03-01  100.0
2017-04-01  240.0

2, 3. 删除工作单位 中位数 个工作单位的行

# Set an index on which to merge the medians
df2 = df.set_index('Date')
# Pandas is smart enough to merge the 4-row meds DataFrame onto the 10-row df2 DataFrame based on matching index values
df2['Median'] = meds 

# Build a boolean mask to pick out "hard workers" and "slackers"
mask = df2['Units of Work'] >= 0.2 * df2['Median']

# "Hard workers," where units of work >= 20% of that date's median
df2[mask]
               Name  Units of Work  Median
Date                                      
2017-01-01   Bob             450.0   450.0
2017-02-01   Alice           300.0   300.0
2017-02-01   Bob             450.0   300.0
2017-03-01   Alice           400.0   100.0
2017-03-01   Larry           100.0   100.0
2017-04-01   Alice          1000.0   240.0
2017-04-01   Bob             240.0   240.0

# Bonus: "slackers," where units of work < 20% of that date's median
df2[~mask]
               Name  Units of Work  Median
Date                                      
2017-02-01   Larry            50.0   300.0
2017-03-01   Bob              11.0   100.0
2017-04-01   Larry            33.0   240.0

4。对于每个日期,将“辛勤劳动者”的数量乘以工作单位的中位数

df2[mask].groupby('Date').size().mul(meds['Units of Work'])
2017-01-01    450.0
2017-02-01    600.0
2017-03-01    200.0
2017-04-01    480.0

【讨论】:

  • 嘿@peter-leimbigler 感谢您的回答。我在 to_datetime 上的 format='%b-%y' 遇到了一些问题。我认为我通过在 read_csv 上添加 parse_dates=[0] 来解决它,但现在我得到一个“AttributeError:'Timestamp' 对象没有属性 'str'”
  • @SteveDallas 好主意回复:parse_dates=[0]。这个新错误来自哪里?如果您还没有,请尝试保留parse_dates=[0] 并注释掉整个to_datetime 行。或者,如果您不介意按字典顺序(Apr、Feb、...)对日期进行排序,您可以尝试不使用 parse_dates 也不要调用 to_datetime 行。然后,如果您愿意,您可以在最后一步将Date 列从字符串转换为日期时间,排序并写入 CSV。
  • 错误出现在 lambda 上。不过,我看到了几件事。看起来正在计算平均值而不是中位数。此外,我看到所有日期行。这是前几行| date | user | work | median | |--------|-------|------|--------| | 6/1/15 | Alice | 1 | 4 | | 6/1/15 | Bob | 7 | 4 |
  • 抱歉,无法确定如何在 cmets 中添加该表。
  • @SteveDallas,这绝对是正在计算的中位数。根据定义,中位数是输入列表的 middle 值。您可以看出这是有效的,因为这个特定中位数列表中的数字也是输入列表的成员(所有输入日期都有奇数行,否则我们会看到 mean中间的两个值)。如果 lambda 抛出错误,只需注释掉整行(我在上面将其称为“to_datetime 行”)并重试。它所做的只是转换为datetime,您可能不需要。
【解决方案2】:

我大约 80% 确定我没有完全理解目标,但这是我的尝试。

import pandas as pd

df = pd.DataFrame({"Date": ["Jan-12", "Jan-12"], "Name": ["Bob", "Alice"], "Work": [400, 300]})

def extract_rows_with_date(df, date):
    return df[df["Date"] == date]

# Extract unique dates
dates = df.Date.unique()

# Creating an empty dataframe dictionary (you get it)
new_df = {"Date": [], "Median": []}

for date in dates:
    # Fun stuff here
    date_df = extract_rows_with_date(df, date)
    median = date_df["Work"].median()

    above_20_median = date_df[date_df["Work"] > (median*20)/100]

    count_above_median = above_20_median.shape[0]

    new_df["Date"].append(date)
    new_df["Median"].append(count_above_median * median)


new_df = pd.DataFrame(new_df)
print(new_df.head())

【讨论】:

  • 嘿,谢谢@wiggy-a。发生了一些奇怪的事情。首先是我的工作清单都是整数。我在列表中看到了几个小数。此外,它只打印前几个日期。知道为什么吗?
猜你喜欢
  • 2021-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-18
  • 2019-08-28
  • 2014-12-07
相关资源
最近更新 更多