こんにちは私は反復問題のベクトル化された(またはより効率的な)解決策を見つけようとしています。私が見つけた唯一の解決策は、複数のループを持つDataFrameの行ごとの反復を必要とします。実際のデータファイルは巨大であるため、私の現在のソリューションは実質的に実行不可能です。ご覧になりたい場合は、最後にラインプロファイラー出力を含めました。実際の問題は非常に複雑なので、簡単な例でこれを説明しようと思います(簡単にするためにかなりの時間がかかりました:)):
2つの着陸帯が並んでいる空港があるとします。各飛行機は着陸し(到着時間)、しばらくの間着陸帯の1つにタクシーを乗せ、その後離陸します(出発時間)。すべてがPandas DataFrameに格納され、次のように到着時間でソートされます(テスト用のより大きなデータセットについてはEDIT2を参照):
PLANE STRIP ARRIVAL DEPARTURE
0 1 85.00 86.00
1 1 87.87 92.76
2 2 88.34 89.72
3 1 88.92 90.88
4 2 90.03 92.77
5 2 90.27 91.95
6 2 92.42 93.58
7 2 94.42 95.58
2つのケースの解決策を探しています:
1。一度に1つのストリップに複数の平面が存在するイベントのリストを作成します。イベントのサブセットを含めないでください(たとえば、有効な[3,4,5]ケースがある場合は[3,4]を表示しません)。リストには、実際のDataFrame行のインデックスを格納する必要があります。この場合の解決策については、関数findSingleEvents()を参照してください(約5ミリ秒実行)。
2。各ストリップに一度に少なくとも1つの平面があるイベントのリストを作成します。イベントのサブセットをカウントせず、最大数のプレーンでイベントを記録するだけです。 (たとえば、[3,4,5]の場合は、[3,4]を表示しません)。単一のストリップで完全に発生するイベントをカウントしないでください。リストには、実際のDataFrame行のインデックスを格納する必要があります。この場合の解決策については、関数findMultiEvents()を参照してください(約15ミリ秒実行)。
作業コード:
import numpy as np
import pandas as pd
import itertools
from __future__ import division
data = [{'PLANE':0, 'STRIP':1, 'ARRIVAL':85.00, 'DEPARTURE':86.00},
{'PLANE':1, 'STRIP':1, 'ARRIVAL':87.87, 'DEPARTURE':92.76},
{'PLANE':2, 'STRIP':2, 'ARRIVAL':88.34, 'DEPARTURE':89.72},
{'PLANE':3, 'STRIP':1, 'ARRIVAL':88.92, 'DEPARTURE':90.88},
{'PLANE':4, 'STRIP':2, 'ARRIVAL':90.03, 'DEPARTURE':92.77},
{'PLANE':5, 'STRIP':2, 'ARRIVAL':90.27, 'DEPARTURE':91.95},
{'PLANE':6, 'STRIP':2, 'ARRIVAL':92.42, 'DEPARTURE':93.58},
{'PLANE':7, 'STRIP':2, 'ARRIVAL':94.42, 'DEPARTURE':95.58}]
df = pd.DataFrame(data, columns = ['PLANE','STRIP','ARRIVAL','DEPARTURE'])
def findSingleEvents(df):
events = []
for row in df.itertuples():
#Create temporary dataframe for each main iteration
dfTemp = df[(row.DEPARTURE>df.ARRIVAL) & (row.ARRIVAL<df.DEPARTURE)]
if len(dfTemp)>1:
#convert index values to integers from long
current_event = [int(v) for v in dfTemp.index.tolist()]
#loop backwards to remove elements that do not comply
for i in reversed(current_event):
if (dfTemp.loc[i].ARRIVAL > dfTemp.DEPARTURE).any():
current_event.remove(i)
events.append(current_event)
#remove duplicate events
events = map(list, set(map(Tuple, events)))
return events
def findMultiEvents(df):
events = []
for row in df.itertuples():
#Create temporary dataframe for each main iteration
dfTemp = df[(row.DEPARTURE>df.ARRIVAL) & (row.ARRIVAL<df.DEPARTURE)]
if len(dfTemp)>1:
#convert index values to integers from long
current_event = [int(v) for v in dfTemp.index.tolist()]
#loop backwards to remove elements that do not comply
for i in reversed(current_event):
if (dfTemp.loc[i].ARRIVAL > dfTemp.DEPARTURE).any():
current_event.remove(i)
#remove elements only on 1 strip
if len(df.iloc[current_event].STRIP.unique()) > 1:
events.append(current_event)
#remove duplicate events
events = map(list, set(map(Tuple, events)))
return events
print findSingleEvents(df[df.STRIP==1])
print findSingleEvents(df[df.STRIP==2])
print findMultiEvents(df)
検証済み出力:
[[1, 3]]
[[4, 5], [4, 6]]
[[1, 3, 4, 5], [1, 4, 6], [1, 2, 3]]
明らかに、これらは効率的でもエレガントなソリューションでもありません。私が持っている巨大なDataFrameでは、これを実行するにはおそらく数時間かかります。かなり前からベクトル化されたアプローチを考えていましたが、しっかりしたものは何も思いつきませんでした。どんなポインタ/ヘルプも大歓迎です! Numpy/Cython/Numbaベースのアプローチも受け付けています。
ありがとう!
PS:リストをどうするか疑問に思ったら:各EVENT
にEVENT
番号を割り当て、上記のデータをマージして個別のデータベースを構築します、およびEVENT
番号は、別の列として使用され、他の目的で使用されます。ケース1の場合、次のようになります。
EVENT PLANE STRIP ARRIVAL DEPARTURE
0 4 2 90.03 92.77
0 5 2 90.27 91.95
1 5 2 90.27 91.95
1 6 2 92.42 95.58
編集:コードとテストデータセットを改訂しました。
EDIT2:以下のコードを使用して、テスト目的で1000行(またはそれ以上)の長さのDataFrameを生成します。 (@ImportanceOfBeingErnestの推奨による)
import random
import pandas as pd
import numpy as np
data = []
for i in range(1000):
arrival = random.uniform(0,1000)
departure = arrival + random.uniform(2.0, 10.0)
data.append({'PLANE':i, 'STRIP':random.randint(1, 2),'ARRIVAL':arrival,'DEPARTURE':departure})
df = pd.DataFrame(data, columns = ['PLANE','STRIP','ARRIVAL','DEPARTURE'])
df = df.sort_values(by=['ARRIVAL'])
df = df.reset_index(drop=True)
df.PLANE = df.index
EDIT3:
受け入れられた回答の修正版。受け入れられた回答は、イベントのサブセットを削除できませんでした。変更されたバージョンは、「有効な[3,4,5]の場合がある場合、[3,4]を表示しない)」というルールを満たしています。
def maximal_subsets_modified(sets):
sets.sort()
maximal_sets = []
s0 = frozenset()
for s in sets:
if not (s > s0) and len(s0) > 1:
not_in_list = True
for x in maximal_sets:
if set(x).issubset(set(s0)):
maximal_sets.remove(x)
if set(s0).issubset(set(x)):
not_in_list = False
if not_in_list:
maximal_sets.append(list(s0))
s0 = s
if len(s0) > 1:
not_in_list = True
for x in maximal_sets:
if set(x).issubset(set(s0)):
maximal_sets.remove(x)
if set(s0).issubset(set(x)):
not_in_list = False
if not_in_list:
maximal_sets.append(list(s0))
return maximal_sets
def maximal_subsets_2_modified(sets, d):
sets.sort()
maximal_sets = []
s0 = frozenset()
for s in sets:
if not (s > s0) and len(s0) > 1 and d.loc[list(s0), 'STRIP'].nunique() == 2:
not_in_list = True
for x in maximal_sets:
if set(x).issubset(set(s0)):
maximal_sets.remove(x)
if set(s0).issubset(set(x)):
not_in_list = False
if not_in_list:
maximal_sets.append(list(s0))
s0 = s
if len(s0) > 1 and d.loc[list(s), 'STRIP'].nunique() == 2:
not_in_list = True
for x in maximal_sets:
if set(x).issubset(set(s0)):
maximal_sets.remove(x)
if set(s0).issubset(set(x)):
not_in_list = False
if not_in_list:
maximal_sets.append(list(s0))
return maximal_sets
# single
def hal_3_modified(d):
sets = np.apply_along_axis(
lambda x: frozenset(d.PLANE.values[(d.PLANE.values <= x[0]) & (d.DEPARTURE.values > x[2])]),
1, d.values
)
return maximal_subsets_modified(sets)
# multi
def hal_5_modified(d):
sets = np.apply_along_axis(
lambda x: frozenset(d.PLANE.values[(d.PLANE.values <= x[0]) & (d.DEPARTURE.values > x[2])]),
1, d.values
)
return maximal_subsets_2_modified(sets, d)
反復する代わりに_DataFrame.apply
_を使用してソリューションを書き直し、最適化として可能な限りnumpy配列を使用しました。 frozenset
を使用したのは、それらが不変でハッシュ可能であり、したがって_Series.unique
_が正しく機能するためです。 _Series.unique
_は、タイプset
の要素で失敗します。
また、d.loc[list(x), 'STRIP'].nunique()
はd.loc[list(x)].STRIP.nunique()
よりもわずかに速いことがわかりました。理由はわかりませんが、以下のソリューションでより高速なステートメントを使用しました。
各行について、出発が現在の到着よりも大きい現在のインデックスのインデックスよりも低い(または等しい)インデックスのセットを作成します。これにより、セットのリストが作成されます。
他のセットのサブセットではない一意のセットを返します(2番目のアルゴリズムでは、両方のSTRIP
sがセットによって参照されるようにさらにフィルター処理します)
numpyレイヤーにドロップダウンし、df.applyを使用する代わりに_np.apply_along_axis
_を使用することで、1つの小さな改善が行われました。 PLANE
は常にデータフレームインデックスと等しく、_df.values
_を使用して基になる行列にアクセスできるため、これが可能です。
最大のサブセットを返すリスト内包表記で大きな改善が見られました
_[list(x) for x in sets if ~np.any(sets > x)]
_
上記はO(n ^ 2)の順序演算です。小さなデータセットでは、これは非常に高速です。ただし、より大きなデータセットでは、このステートメントがボトルネックになります。これを最適化するには、最初にsets
を並べ替え、要素をもう一度ループして最大のサブセットを見つけます。ソートしたら、elem [n]がelem [n + 1]のサブセットではないことを確認して、elem [n]が最大のサブセットであるかどうかを判断するだけで十分です。ソート手順は、2つの要素を_<
_操作と比較します
私の元の実装では、OPの試みと比較してパフォーマンスが大幅に向上しましたが、次のグラフに示すように、アルゴリズムは指数関数的に順序付けられていました。
findMultiEvents
、_hal_2
_、および_hal_5
_のタイミングのみを示します。 findSinglEvents
、_hal_1
_、および_hal_3
_の相対的なパフォーマンスも同様に同等です。
下にスクロールして、ベンチマークコードを確認してください。
findMumtiEvents
&_hal_2
_のベンチマークを、指数因子によって効率が低いことが明らかになったときに停止したことに注意してください
_def maximal_subsets(sets):
sets.sort()
maximal_sets = []
s0 = frozenset()
for s in sets[::-1]:
if s0 > s or len(s) < 2:
continue
maximal_sets.append(list(s))
s0 = s
return maximal_sets
def maximal_subsets_2(sets, d):
sets.sort()
maximal_sets = []
s0 = frozenset()
for s in sets[::-1]:
if s0 > s or len(s) < 2 or d.loc[list(s), 'STRIP'].nunique() < 2:
continue
maximal_sets.append(list(s))
s0 = s
return maximal_sets
# single
def hal_3(d):
sets = np.apply_along_axis(
lambda x: frozenset(d.PLANE.values[(d.PLANE.values <= x[0]) & (d.DEPARTURE.values > x[2])]),
1, d.values
)
return maximal_subsets(sets)
# multi
def hal_5(d):
sets = np.apply_along_axis(
lambda x: frozenset(d.PLANE.values[(d.PLANE.values <= x[0]) & (d.DEPARTURE.values > x[2])]),
1, d.values
)
return maximal_subsets_2(sets, d)
_
_# findSingleEvents
def hal_1(d):
sets = d.apply(
lambda x: frozenset(
d.index.values[(d.index.values <= x.name) & (d.DEPARTURE.values > x.ARRIVAL)]
),
axis=1
).unique()
return [list(x) for x in sets if ~np.any(sets > x) and len(x) > 1]
# findMultiEvents
def hal_2(d):
sets = d.apply(
lambda x: frozenset(
d.index.values[(d.index.values <= x.name) & (d.DEPARTURE.values > x.ARRIVAL)]
),
axis=1
).unique()
return [list(x) for x in sets
if ~np.any(sets > x) and
len(x) > 1 and
d.loc[list(x), 'STRIP'].nunique() == 2]
_
出力はOPの実装と同じです。
_hal_1(df[df.STRIP==1])
[[1, 3]]
hal_1(df[df.STRIP==2])
[[4, 5], [4, 6]]
hal_2(df)
[[1, 2, 3], [1, 3, 4, 5], [1, 4, 6]]
hal_3(df[df.STRIP==1])
[[1, 3]]
hal_3(df[df.STRIP==2])
[[4, 5], [4, 6]]
hal_5(df)
[[1, 2, 3], [1, 3, 4, 5], [1, 4, 6]]
_
_os: windows 10
python: 3.6 (Anaconda)
pandas: 0.22.0
numpy: 1.14.3
_
_import random
def mk_random_df(n):
data = []
for i in range(n):
arrival = random.uniform(0,1000)
departure = arrival + random.uniform(2.0, 10.0)
data.append({'PLANE':i, 'STRIP':random.randint(1, 2),'ARRIVAL':arrival,'DEPARTURE':departure})
df = pd.DataFrame(data, columns = ['PLANE','STRIP','ARRIVAL','DEPARTURE'])
df = df.sort_values(by=['ARRIVAL'])
df = df.reset_index(drop=True)
df.PLANE = df.index
return df
dfs = {i: mk_random_df(100*(2**i)) for i in range(0, 10)}
times, times_2, times_5 = [], [], []
for i, v in dfs.items():
if i < 5:
t = %timeit -o -n 3 -r 3 findMultiEvents(v)
times.append({'size(pow. of 2)': i, 'timings': t})
for i, v in dfs.items():
t = %timeit -o -n 3 -r 3 hal_5(v)
times_5.append({'size(pow. of 2)': i, 'timings': t})
for i, v in dfs.items():
if i < 9:
t = %timeit -o -n 3 -r 3 hal_2(v)
times_2.append({'size(pow. of 2)': i, 'timings': t})
_