2016-09-26 18 views
7

私はトランザクションデータを読むことができるように私のデータを変換する助けが必要です。列内の条件に基づいてグループ/クラスを作成する

ビジネスケース

私はイベントのいくつかのグループまたはクラスを作成するために一緒にグループにいくつかの関連する取引をしようとしています。このデータセットは、さまざまな欠席イベントの外出中の労働者を表します。休暇イベントクラスの365日以内に行われた取引に基づいて、1つのクラスの葉を作成したいと考えています。傾向をグラフ化するために、クラスに番号を付けてシーケンス/パターンを取得する必要があります。

私のコードでは、最初のイベントがいつ発生したかを知ることができ、新しいクラスがいつ始まるかを識別できますが、各トランザクションをクラスにバケット化しません。

要件:

  • タグのすべての行に基づいて、クラスを残して、何が彼らがに分類されます。
  • 各ユニーク離脱イベントに番号を付けます。この例のインデックス0はユニーク離脱イベント2、インデックス1はユニーク離脱イベント2、インデックス3はユニーク離脱イベント2、ANDインデックス4はユニーク離脱イベント1などです。

希望の出力の列に「希望出力」というラベルが付けられています。 1人あたりもっと多くの行/イベントが存在することに注意してください。もっと多くの人がいるかもしれません。

いくつかのデータ

import pandas as pd 

data = {'Employee ID': ["100", "100", "100","100","200","200","200","300"], 
     'Effective Date': ["2016-01-01","2015-06-05","2014-07-01","2013-01-01","2016-01-01","2015-01-01","2013-01-01","2014-01"], 
     'Desired Output': ["Unique Leave Event 2","Unique Leave Event 2","Unique Leave Event 2","Unique Leave Event 1","Unique Leave Event 2","Unique Leave Event 2","Unique Leave Event 1","Unique Leave Event 1"]} 
df = pd.DataFrame(data, columns=['Employee ID','Effective Date','Desired Output']) 

私はこれは少し不格好ですが、それは少なくともあなたの小さなたとえば右の出力が得られ

df['Effective Date'] = df['Effective Date'].astype('datetime64[ns]') 
df['EmplidShift'] = df['Employee ID'].shift(-1) 
df['Effdt-Shift'] = df['Effective Date'].shift(-1) 
df['Prior Row in Same Emplid Class'] = "No" 
df['Effdt Diff'] = df['Effdt-Shift'] - df['Effective Date'] 
df['Effdt Diff'] = (pd.to_timedelta(df['Effdt Diff'], unit='d') + pd.to_timedelta(1,unit='s')).astype('timedelta64[D]') 
df['Cumul. Count'] = df.groupby('Employee ID').cumcount() 


df['Groupby'] = df.groupby('Employee ID')['Cumul. Count'].transform('max') 
df['First Row Appears?'] = "" 
df['First Row Appears?'][df['Cumul. Count'] == df['Groupby']] = "First Row" 
df['Prior Row in Same Emplid Class'][ df['Employee ID'] == df['EmplidShift']] = "Yes" 

df['Prior Row in Same Emplid Class'][ df['Employee ID'] == df['EmplidShift']] = "Yes" 

df['Effdt > 1 Yr?'] = ""           
df['Effdt > 1 Yr?'][ ((df['Prior Row in Same Emplid Class'] == "Yes") & (df['Effdt Diff'] < -365)) ] = "Yes" 

df['Unique Leave Event'] = "" 
df['Unique Leave Event'][ (df['Effdt > 1 Yr?'] == "Yes") | (df['First Row Appears?'] == "First Row") ] = "Unique Leave Event" 

df 

答えて

2

これは、データフレームをループしたり、繰り返したりせずに行うことができます。 Wes McKinneyでは、.apply()をgroupByオブジェクトに使用し、groupbyオブジェクトに適用する関数を定義することができます。 .shift()like here)でこれを使用すると、ループを使用せずに結果を得ることができます。

簡潔な例:

# Group by Employee ID 
grouped = df.groupby("Employee ID") 
# Define function 
def get_unique_events(group): 
    # Convert to date and sort by date, like @Khris did 
    group["Effective Date"] = pd.to_datetime(group["Effective Date"]) 
    group = group.sort_values("Effective Date") 
    event_series = (group["Effective Date"] - group["Effective Date"].shift(1) > pd.Timedelta('365 days')).apply(lambda x: int(x)).cumsum()+1 
    return event_series 

event_df = pd.DataFrame(grouped.apply(get_unique_events).rename("Unique Event")).reset_index(level=0) 
df = pd.merge(df, event_df[['Unique Event']], left_index=True, right_index=True) 
df['Output'] = df['Unique Event'].apply(lambda x: "Unique Leave Event " + str(x)) 
df['Match'] = df['Desired Output'] == df['Output'] 

print(df) 

出力:

Employee ID Effective Date  Desired Output Unique Event \ 
3   100  2013-01-01 Unique Leave Event 1    1 
2   100  2014-07-01 Unique Leave Event 2    2 
1   100  2015-06-05 Unique Leave Event 2    2 
0   100  2016-01-01 Unique Leave Event 2    2 
6   200  2013-01-01 Unique Leave Event 1    1 
5   200  2015-01-01 Unique Leave Event 2    2 
4   200  2016-01-01 Unique Leave Event 2    2 
7   300  2014-01 Unique Leave Event 1    1 

       Output Match 
3 Unique Leave Event 1 True 
2 Unique Leave Event 2 True 
1 Unique Leave Event 2 True 
0 Unique Leave Event 2 True 
6 Unique Leave Event 1 True 
5 Unique Leave Event 2 True 
4 Unique Leave Event 2 True 
7 Unique Leave Event 1 True 

わかりやすくするためにもっと詳細な例:

import pandas as pd 

data = {'Employee ID': ["100", "100", "100","100","200","200","200","300"], 
     'Effective Date': ["2016-01-01","2015-06-05","2014-07-01","2013-01-01","2016-01-01","2015-01-01","2013-01-01","2014-01"], 
     'Desired Output': ["Unique Leave Event 2","Unique Leave Event 2","Unique Leave Event 2","Unique Leave Event 1","Unique Leave Event 2","Unique Leave Event 2","Unique Leave Event 1","Unique Leave Event 1"]} 
df = pd.DataFrame(data, columns=['Employee ID','Effective Date','Desired Output']) 

# Group by Employee ID 
grouped = df.groupby("Employee ID") 

# Define a function to get the unique events 
def get_unique_events(group): 
    # Convert to date and sort by date, like @Khris did 
    group["Effective Date"] = pd.to_datetime(group["Effective Date"]) 
    group = group.sort_values("Effective Date") 
    # Define a series of booleans to determine whether the time between dates is over 365 days 
    # Use .shift(1) to look back one row 
    is_year = group["Effective Date"] - group["Effective Date"].shift(1) > pd.Timedelta('365 days') 
    # Convert booleans to integers (0 for False, 1 for True) 
    is_year_int = is_year.apply(lambda x: int(x))  
    # Use the cumulative sum function in pandas to get the cumulative adjustment from the first date. 
    # Add one to start the first event as 1 instead of 0 
    event_series = is_year_int.cumsum() + 1 
    return event_series 

# Run function on df and put results into a new dataframe 
# Convert Employee ID back from an index to a column with .reset_index(level=0) 
event_df = pd.DataFrame(grouped.apply(get_unique_events).rename("Unique Event")).reset_index(level=0) 

# Merge the dataframes 
df = pd.merge(df, event_df[['Unique Event']], left_index=True, right_index=True) 

# Add string to match desired format 
df['Output'] = df['Unique Event'].apply(lambda x: "Unique Leave Event " + str(x)) 

# Check to see if output matches desired output 
df['Match'] = df['Desired Output'] == df['Output'] 

print(df) 

同じ出力を得る:

Employee ID Effective Date  Desired Output Unique Event \ 
3   100  2013-01-01 Unique Leave Event 1    1 
2   100  2014-07-01 Unique Leave Event 2    2 
1   100  2015-06-05 Unique Leave Event 2    2 
0   100  2016-01-01 Unique Leave Event 2    2 
6   200  2013-01-01 Unique Leave Event 1    1 
5   200  2015-01-01 Unique Leave Event 2    2 
4   200  2016-01-01 Unique Leave Event 2    2 
7   300  2014-01 Unique Leave Event 1    1 

       Output Match 
3 Unique Leave Event 1 True 
2 Unique Leave Event 2 True 
1 Unique Leave Event 2 True 
0 Unique Leave Event 2 True 
6 Unique Leave Event 1 True 
5 Unique Leave Event 2 True 
4 Unique Leave Event 2 True 
7 Unique Leave Event 1 True 
+0

これは洗練されたソリューションです。 OPが本当に巨大なデータフレームを使用しているにもかかわらず、データの内容から判断すると、「マージ」に唯一の危険が存在する可能性があります。 – Khris

3

を試したいくつかのコード:

import pandas as pd 

data = {'Employee ID': ["100", "100", "100","100","200","200","200","300"], 
     'Effective Date': ["2016-01-01","2015-06-05","2014-07-01","2013-01-01","2016-01-01","2015-01-01","2013-01-01","2014-01-01"], 
     'Desired Output': ["Unique Leave Event 2","Unique Leave Event 2","Unique Leave Event 2","Unique Leave Event 1","Unique Leave Event 2","Unique Leave Event 2","Unique Leave Event 1","Unique Leave Event 1"]} 
df = pd.DataFrame(data, columns=['Employee ID','Effective Date','Desired Output']) 

df["Effective Date"] = pd.to_datetime(df["Effective Date"]) 
df = df.sort_values(["Employee ID","Effective Date"]).reset_index(drop=True) 

for i,_ in df.iterrows(): 
    df.ix[0,"Result"] = "Unique Leave Event 1" 
    if i < len(df)-1: 
    if df.ix[i+1,"Employee ID"] == df.ix[i,"Employee ID"]: 
     if df.ix[i+1,"Effective Date"] - df.ix[i,"Effective Date"] > pd.Timedelta('365 days'): 
     df.ix[i+1,"Result"] = "Unique Leave Event " + str(int(df.ix[i,"Result"].split()[-1])+1) 
     else: 
     df.ix[i+1,"Result"] = df.ix[i,"Result"] 
    else: 
     df.ix[i+1,"Result"] = "Unique Leave Event 1" 

注このコードでは、最初の行に常に文字列Unique Leave Event 1が含まれていると想定しています。

EDIT:何らかの説明。

まず、日付をdatetime形式に変換し、その後、すべての従業員IDの日付が昇順になるようにデータフレームの順序を変更します。

次に、組み込みintイテレータiterrowsを使用して、フレームの行を繰り返し処理します。 _for i,_は、イテレータが行番号と行の両方を返すため、私が使用しない2番目の変数のプレースホルダにすぎません。ここには番号が必要です。

イテレータでは、行ごとの比較を行っていますので、デフォルトでは最初の行を手で入力してからi+1番目の行に割り当てます。私は最初の行の値は知っているが、最後の行の値は知っていないので、これは好きです。次に、i+1が最後の反復でインデックスエラーを与えるため、if -safeguard内のi番目の行とi+1番目の行を比較します。

ループでは、最初にEmployee IDが2つの行の間で変更されているかどうかを確認します。それがない場合は、2つの行の日付を比較し、365日以上離れているかどうかを確認します。これが当てはまる場合は、という文字列をi番目の行から読み取り、数字を1つ増やしてi+1 -rowに書き込んでください。日付が近い場合は、前の行から文字列をコピーするだけです。

一方、Employee IDが変更された場合は、最初から"Unique Leave Event 1"と書いてください。

注1:iterrows()には設定するオプションがありません。そのため、サブセットのみを反復できません。

注2:組み込みイテレータの1つを使用して繰り返し、それ以外の方法で問題を解決できない場合にのみ繰り返します。

注3:反復で値を割り当てる場合は、常にix,loc、またはilocを使用してください。

+0

ありがとう!あなたはこれをどうやって行ったかに関するコメントをお願いしますか? – Christopher

+0

こんにちは、お待たせして申し訳ありません、私は仕事からここにコメントし、私たちは3日間の週末を過ごしました。今私はいくつかのコメントを追加します。 – Khris

関連する問題