2016-11-04 4 views
0

私は単純なPandasdataframeです。ここで、各行は個人と日付範囲を表しています。それぞれの人のために、ハードコードされた範囲(変数period_startperiod_endで定義される)の何パーセントの日数がdataframeのさまざまなエントリに渡ってカバーされているか知りたいと思います。パンダのグループ機能で範囲をカバーする日の割合を計算する

私はPandasでこれを行う簡単な方法があると思いますが、見つけられませんでした。私は複数のdataframesといくつかのネストされたループを持つソリューションを持っていますが、これは規模が非効率です。 Pandasを利用すると、これをより効果的に行うにはどうすればよいですか?私はgroupbyが理にかなっていますが、範囲が2つの列にまたがって重なり合っている場合には、その方法を理解していないと思います。

import pandas as pd 
from datetime import datetime 
df = pd.DataFrame(data=[['2016-01-01', '2016-01-31', 'A'], 
         ['2016-02-02', '2016-02-10', 'A'], 
         ['2016-03-01', '2016-04-01', 'A'], 
         ['2016-01-01', '2016-03-01', 'B']], 
        columns=['startdate', 'enddate', 'person']) 

# start and end date 
period_start = datetime(year=2016, month=01, day=01) 
period_end = datetime(year=2016, month=12, day=31) 

# dates_dfculate totals days 
total_days = (period_end-period_start).days + 1 

# convert columns to dates 
df['startdate']= pd.to_datetime(df['startdate'], format='%Y-%m-%d') 
df['enddate']= pd.to_datetime(df['enddate'], format='%Y-%m-%d') 

# create a TimeIndex dataframe with columns for each person 
rng = pd.date_range(period_start, periods=total_days, freq='D') 
people = list(set(df['person'].tolist())) 
dates_df = pd.DataFrame(columns=[people], index=rng).fillna(False) 

# loop over each date (index) 
for index, row in dates_df.iterrows(): 

    # loop over each column (person) 
    for person in people: 
     tmp = df[df['person'] == person] 

     # loop over each each entry for the person 
     for index1, row1 in tmp.iterrows(): 

      # check if the date is date index in dates_df is within range 
      value = row1['startdate'] <= index <= row1['enddate'] 

      # if it's not already set to true, set it to true 
      if dates_df.ix[index, person] == False and value == True: 
       dates_df.ix[index, person] = True 

# for each person, show the percentage of days in range that are covered 
for person in people: 
    print person, sum(dates_df[person].tolist())/float(total_days) 

所望の出力:

A 0.196721311475 
B 0.166666666667 
+0

あなたの望む出力は***に似ていますか? – Abdou

+0

これは単なる一人の人のループであり、その割合を印刷します。質問に出力を追加しました。 – user2242044

答えて

1

これはあなたが範囲に包括的になりたいの総日数に1を追加しているので、私は推測している、それであってもよいが、必要に応じてそれを編集してください:)あなたは正しい軌道に乗っている

import pandas as pd 
from datetime import datetime 

df = pd.DataFrame(data=[['2016-01-01', '2016-01-31', 'A'], 
         ['2016-02-02', '2016-02-10', 'A'], 
         ['2016-03-01', '2016-04-01', 'A'], 
         ['2016-01-01', '2016-03-01', 'B']], 
        columns=['startdate', 'enddate', 'person']) 

# start and end date 
period_start = datetime(year=2016, month=1, day=1) 
period_end = datetime(year=2016, month=12, day=31) 

# convert columns to dates 
df['startdate']= pd.to_datetime(df['startdate'], format='%Y-%m-%d') 
df['enddate']= pd.to_datetime(df['enddate'], format='%Y-%m-%d') 
df['days'] = df.apply(lambda x: max((min(x.enddate, period_end) - max(x.startdate, period_start)).days + 1, 0), axis=1) 

#percentage of days in range by person 
people_pct = df.groupby('person').apply(lambda x: x.days.sum()/((period_end - period_start).days + 1)) 
print(people_pct.head()) 

----------------- 
    person 
    A 0.196721 
    B 0.166667 

- パンダgroupbyは、データをセグメント化のための素晴らしいですが、本当の力は、いずれかの一般的な数学の変換を行うことができます.apply()機能、から来ています(平均値、標準偏差など)、またはこの場合のようにカスタム関数を使用します。

適用内のlambdaは、「グループ内の各行/列(axisに応じて)に対して、このカスタム関数を実行してSeriesを返します」と言います。

これはあなたの質問をカバーしていますが、これはまだユニークな日を検出するには欠けているので、行が重複していないと仮定しています。

+0

これは本当に素晴らしいクリーンなソリューションです。 – user2242044

+0

ありがとう! http://pandas.pydata.org/pandas-docs/stable/groupby.htmlを調べることをお勧めします。 – dylanjf

+0

100%以上のパーセンテージを得ることができるオーバーラップがある場合、コードが2倍になることを認識しています。たとえば、データが '[[2017-01-01 '、' 2017-01-02 '、' A ']、[' 2017-01-01 '、' 2017-01-03、 'A' ]] 'これを修正するためのアイデアは? – user2242044

関連する問題