我有一个如下所示的pandas数据框:
ID date close
1 09/15/07 123.45
2 06/01/08 130.13
3 10/25/08 132.01
4 05/13/09 118.34
5 11/07/09 145.99
6 11/15/09 146.73
7 07/03/11 171.10
我想删除任何重叠的行。
重叠行定义为另一行X天内的任何行。例如,如果X = 365,则结果应为:
ID date close
1 09/15/07 123.45
3 10/25/08 132.01
5 11/07/09 145.99
7 07/03/11 171.10
如果X = 50,结果应为:
ID date close
1 09/15/07 123.45
2 06/01/08 130.13
3 10/25/08 132.01
4 05/13/09 118.34
5 11/07/09 145.99
7 07/03/11 171.10
我在这里看了几个问题,但没有找到正确的方法。例如,Pandas check for overlapping dates in multiple rows和Fastest way to eliminate specific dates from pandas dataframe相似,但并不能完全满足我的需求。
我今天有以下丑陋的代码适用于小X值但是当X变大时(例如,当X = 365时),它会删除除原始日期之外的所有日期。
filter_dates = []
for index, row in df.iterrows():
if observation_time == 'D':
for i in range(1, observation_period):
filter_dates.append((index.date() + timedelta(days=i)))
df = df[~df.index.isin(filter_dates)]
任何帮助/指示都将不胜感激!
澄清:
解决方案需要查看每一行,而不仅仅是第一行。
答案 0 :(得分:3)
您可以添加新列来过滤结果:
df['filter'] = df['date'] - df['date'][0]
df['filter'] = df['filter'].apply(lambda x: x.days)
然后按365过滤使用:
df[df['filter']%365==0]
答案 1 :(得分:2)
我找到了另一个解决方案(如果你想查看旧的,可以查看编辑历史记录)。这是我提出的最佳解决方案。它仍保留第一个连续记录,但可以进行调整以保持记录按时间顺序排列(最后提供)。
target = df.iloc[0] # Get the first item in the dataframe
day_diff = abs(target.date - df.date) # Get the differences of all the other dates from the first item
day_diff = day_diff.reset_index().sort_values(['date', 'index']) # Reset the index and then sort by date and original index so we can maintain the order of the dates
day_diff.columns = ['old_index', 'date'] # rename old index column because of name clash
good_ids = day_diff.groupby(day_diff.date.dt.days // days).first().old_index.values # Group the dates by range and then get the first item from each group
df.iloc[good_ids]
我再次进行了一些测试,将其与QuickBeam的方法进行比较。使用的DataFrame是随机排序的600,000行和按日期排序的DataFrame,有73,000行:
我的方法:
DataFrame days time
600k/random 2 1 loop, best of 3: 5.03 s per loop
ordered 2 1 loop, best of 3: 564 ms per loop
600k/random 50 1 loop, best of 3: 5.17 s per loop
ordered 50 1 loop, best of 3: 583 ms per loo
600k/random 365 1 loop, best of 3: 5.16 s per loop
ordered 365 1 loop, best of 3: 577 ms per loop
QuickBeam的方法:
DataFrame days time
600k/random 2 1 loop, best of 3: 52.8 s per loop
ordered 2 1 loop, best of 3: 4.89 s per loop
600k/random 50 1 loop, best of 3: 53 s per loop
ordered 50 1 loop, best of 3: 4.53 s per loop
600k/random 365 1 loop, best of 3: 53.7 s per loop
ordered 365 1 loop, best of 3: 4.49 s per loop
所以是的,也许我是小竞争对手......
用于测试的确切函数:
def my_filter(df, days):
target = df.iloc[0]
day_diff = abs(target.date - df.date)
day_diff = day_diff.reset_index().sort_values(['date', 'index'])
day_diff.columns = ['old_index', 'date']
good_ids = day_diff.groupby(day_diff.date.dt.days // days).first().old_index.values
return df.iloc[good_ids]
def quickbeam_filter(df, days):
filter_ids = [0]
last_day = df.loc[0, "date"]
for index, row in df[1:].iterrows():
if (row["date"] - last_day).days > days:
filter_ids.append(index)
last_day = row["date"]
return df.loc[filter_ids,:]
如果你想获得在某个范围内开始的所有日期,这对我来说更有意义,你可以使用这个版本:
def my_filter(df, days):
target = df.iloc[0]
day_diff = abs(target.date - df.date)
day_diff = day_diff.sort_values('date')
good_ids = day_diff.groupby(day_diff.date.dt.days // days).first().index.values
return df.iloc[good_ids]
答案 2 :(得分:1)
我的方法是首先计算距离矩阵
distM = np.array([[np.timedelta64(abs(x-y),'D').astype(int) for y in df.date] for x in df.date])
在你的例子中,这将是这样的
[[ 0 260 406 606 784 792 1387]
[ 260 0 146 346 524 532 1127]
[ 406 146 0 200 378 386 981]
[ 606 346 200 0 178 186 781]
[ 784 524 378 178 0 8 603]
[ 792 532 386 186 8 0 595]
[1387 1127 981 781 603 595 0]]
由于向下迭代,我们只关心与顶部三角形的距离,因此我们通过保持顶部并将365的最小值设置为大数M
来修改数组{}} ll使用10,000
distM[np.triu(distM) <= 365] = 10000
然后在新的距离矩阵上取argmin
,以决定要保留哪些数据帧行。
remove = np.unique(np.argmin(distM,axis=1))
df = df.iloc[remove,:]
一起......
distM = np.array([[np.timedelta64(abs(x-y),'D').astype(int) for y in df.date] for x in df.date])
distM[np.triu(distM)<= 365] = 10000
remove = np.unique(np.argmin(distM,axis=1))
df = df.iloc[remove,:]
答案 3 :(得分:0)
我刚刚使用了一种基本方法(基本上它是OP方法的调整版本),没有花哨的numpy或pandas ops,而是线性而不是二次复杂度(当符合距离矩阵方法时)。
但是(作为Cory Madden),我假设数据是根据日期列进行排序的。
我希望这是正确的:
数据框 - &gt;我在这里使用熊猫索引:
import pandas as pd
df = pd.DataFrame({'date': ["2007-09-15","2008-06-01","2008-10-25",
"2009-05-13","2009-11-07", "2009-11-15", "2011-07-03"],
'close':[123.45, 130.13, 132.01, 118.34,
145.99, 146.73, 171.10]})
df["date"]=pd.to_datetime(df["date"])
以下代码块可以很容易地在函数中包装并为X = 365编译正确的数据帧索引:
X = 365
filter_ids = [0]
last_day = df.loc[0, "date"]
for index, row in df[1:].iterrows():
if (row["date"] - last_day).days > X:
filter_ids.append(index)
last_day = row["date"]
结果:
print(df.loc[filter_ids,:])
close date
0 123.45 2007-09-15
2 132.01 2008-10-25
4 145.99 2009-11-07
6 171.10 2011-07-03
请注意,由于索引从零开始,索引会移动一个。
我只想评论线性与四次复杂性 我的解决方案具有线性时间复杂度,只需查看数据帧的每一行。 Cory maddens解决方案具有二次复杂度:在每次迭代中,访问数据帧的每一行。但是,如果X(日差)很大,我们可能会丢弃大部分数据集,只执行很少的迭代。
为此,可能需要考虑X=2
数据集的以下最坏情况:
df = pd.DataFrame({'date':pd.date_range(start='01.01.1900', end='01.01.2100', freq='D')})
在我的机器上,以下代码产生:
%%timeit
X = 2
filter_ids = [0]
last_day = df.loc[0, "date"]
for index, row in df[1:].iterrows():
if (row["date"] -last_day).days > X:
filter_ids.append(index)
last_day = row["date"]
1 loop, best of 3: 7.06 s per loop
和
day_diffs = abs(df.iloc[0].date - df.date).dt.days
i = 0
days = 2
idx = day_diffs.index[i]
good_ids = {idx}
while True:
try:
current_row = day_diffs[idx]
day_diffs = day_diffs.iloc[1:]
records_not_overlapping = (day_diffs - current_row) > days
idx = records_not_overlapping[records_not_overlapping == True].index[0]
good_ids.add(idx)
except IndexError:
break
1 loop, best of 3: 3min 16s per loop
答案 4 :(得分:0)
对于那些寻找适合我的答案的人来说,这是(建立在@ Quickbeam2k1的答案上):
X = 50 #or whatever value you want
remove_ids = []
last_day = df.loc[0, "date"]
for index, row in df[1:].iterrows():
if np.busday_count(last_day, df.loc[index, "date"]) < X:
remove_ids.append(index)
else:
last_day = df.loc[index, "date"]