Tính toán chồng chéo phạm vi ngày hiệu quả trong python?


85

Tôi có hai phạm vi ngày trong đó mỗi phạm vi được xác định bởi ngày bắt đầu và ngày kết thúc (rõ ràng là phiên bản datetime.date ()). Hai phạm vi có thể trùng nhau hoặc không. Tôi cần số ngày trùng lặp. Tất nhiên tôi có thể điền trước hai tập hợp với tất cả các ngày trong cả hai phạm vi và thực hiện một giao điểm đã đặt nhưng điều này có thể không hiệu quả ... có cách nào tốt hơn ngoài giải pháp khác bằng cách sử dụng phần if-elif dài bao gồm tất cả các trường hợp không?

Câu trả lời:


174
  • Xác định ngày muộn nhất trong hai ngày bắt đầu và ngày sớm nhất trong hai ngày kết thúc.
  • Tính toán thời gian bằng cách trừ chúng.
  • Nếu delta là số dương, đó là số ngày trùng lặp.

Đây là một ví dụ tính toán:

>>> from datetime import datetime
>>> from collections import namedtuple
>>> Range = namedtuple('Range', ['start', 'end'])

>>> r1 = Range(start=datetime(2012, 1, 15), end=datetime(2012, 5, 10))
>>> r2 = Range(start=datetime(2012, 3, 20), end=datetime(2012, 9, 15))
>>> latest_start = max(r1.start, r2.start)
>>> earliest_end = min(r1.end, r2.end)
>>> delta = (earliest_end - latest_start).days + 1
>>> overlap = max(0, delta)
>>> overlap
52

1
+1 giải pháp rất hay. Mặc dù vậy, điều này không hoàn toàn hiệu quả đối với những ngày có đầy đủ trong ngày khác. Để đơn giản hóa số nguyên: Phạm vi (1,4) và Phạm vi (2,3) trả về 1
bóng tối

3
@darkless Trên thực tế, nó trả về 2 là chính xác . Hãy thử các đầu vào này r1 = Range(start=datetime(2012, 1, 1), end=datetime(2012, 1, 4)); r2 = Range(start=datetime(2012, 1, 2), end=datetime(2012, 1, 3)). Tôi nghĩ rằng bạn đã bỏ lỡ +1trong tính toán chồng chéo (cần thiết vì khoảng thời gian được đóng ở cả hai đầu).
Raymond Hettinger

Ồ, bạn hoàn toàn đúng, có vẻ như tôi đã bỏ qua điều đó. Cảm ơn bạn :)
darkless

1
Nếu bạn muốn tính 2 lần thay vì 2 ngày thì sao? @RaymondHettinger
Eric

Nếu bạn sử dụng các đối tượng datetime với thời gian mà bạn có thể thay vì .days, hãy viết .total_seconds ().
ErikXIII

10

Các cuộc gọi hàm đắt hơn các phép toán số học.

Cách nhanh nhất để làm điều này bao gồm 2 phép trừ và 1 min ():

min(r1.end - r2.start, r2.end - r1.start).days + 1

so với sản phẩm tốt nhất tiếp theo cần 1 phép trừ, 1 min () và max ():

(min(r1.end, r2.end) - max(r1.start, r2.start)).days + 1

Tất nhiên với cả hai biểu thức, bạn vẫn cần phải kiểm tra sự chồng chéo tích cực.


1
Phương thức này sẽ không trả về câu trả lời đúng luôn luôn. ví dụ: Range = namedtuple('Range', ['start', 'end']) r1 = Range(start=datetime(2016, 6, 15), end=datetime(2016, 6, 15)) r2 = Range(start=datetime(2016, 6, 11), end=datetime(2016, 6, 18)) print min(r1.end - r2.start, r2.end - r1.start).days + 1sẽ in 4 nơi nó được cho là in 1
tkyass

Tôi gặp lỗi chuỗi không rõ ràng khi sử dụng phương trình đầu tiên. Tôi có cần một thư viện cụ thể không?
Arthur D. Howland

6

Tôi đã triển khai một lớp TimeRange như bạn có thể thấy bên dưới.

Đầu tiên get_overlapped_range phủ định tất cả các tùy chọn không bị chồng chéo bằng một điều kiện đơn giản, sau đó tính toán phạm vi bị chồng chéo bằng cách xem xét tất cả các tùy chọn có thể.

Để có số ngày, bạn sẽ cần lấy giá trị TimeRange được trả về từ get_overlapped_range và chia thời lượng cho 60 * 60 * 24.

class TimeRange(object):
    def __init__(self, start, end):
        self.start = start
        self.end = end
        self.duration = self.end - self.start

    def is_overlapped(self, time_range):
        if max(self.start, time_range.start) < min(self.end, time_range.end):
            return True
        else:
            return False

    def get_overlapped_range(self, time_range):
        if not self.is_overlapped(time_range):
            return

        if time_range.start >= self.start:
            if self.end >= time_range.end:
                return TimeRange(time_range.start, time_range.end)
            else:
                return TimeRange(time_range.start, self.end)
        elif time_range.start < self.start:
            if time_range.end >= self.end:
                return TimeRange(self.start, self.end)
            else:
                return TimeRange(self.start, time_range.end)

    def __repr__(self):
        return '{0} ------> {1}'.format(*[time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(d))
                                          for d in [self.start, self.end]])

@ L.Guthardt Đồng ý, nhưng giải pháp này được tổ chức, và đến với nhiều chức năng hơn
Elad Sofer

1
Ok ... điều đó thật tuyệt khi có nhiều chức năng hơn, nhưng thực sự trên StackOverflow, một câu trả lời sẽ phù hợp với nhu cầu được chỉ định của OP. Vì vậy, không hơn không kém. :)
L. Guthardt

5

Bạn có thể sử dụng gói datetimerange: https://pypi.org/project/DateTimeRange/

from datetimerange import DateTimeRange
time_range1 = DateTimeRange("2015-01-01T00:00:00+0900", "2015-01-04T00:20:00+0900") 
time_range2 = DateTimeRange("2015-01-01T00:00:10+0900", "2015-01-04T00:20:00+0900")
tem3 = time_range1.intersection(time_range2)
if tem3.NOT_A_TIME_STR == 'NaT':  # No overlap
    S_Time = 0
else: # Output the overlap seconds
    S_Time = tem3.timedelta.total_seconds()

"2015-01-01T00: 00: 00 + 0900" bên trong DateTimeRange () cũng có thể là định dạng ngày giờ, như Dấu thời gian ('2017-08-30 20:36:25').


1
Cảm ơn, Chỉ cần xem qua tài liệu về DateTimeRangegói và có vẻ như họ hỗ trợ is_intersectiontrả về giá trị boolean nguyên bản (Đúng hoặc Sai) tùy thuộc vào việc có hay không có sự giao nhau giữa hai phạm vi ngày. Vì vậy, ví dụ của bạn: time_range1.is_intersection(time_range2)sẽ quay trở lại Truenếu chúng giao nhauFalse
Deep

3

Mã giả:

 1 + max( -1, min( a.dateEnd, b.dateEnd) - max( a.dateStart, b.dateStart) )

0
def get_overlap(r1,r2):
    latest_start=max(r1[0],r2[0])
    earliest_end=min(r1[1],r2[1])
    delta=(earliest_end-latest_start).days
    if delta>0:
        return delta+1
    else:
        return 0

0

Được rồi, giải pháp của tôi hơi rắc rối vì df của tôi sử dụng tất cả các chuỗi - nhưng giả sử bạn có các cột sau, 2 trong số đó đã được sửa, đó là "Năm tài chính" của bạn. PoP là "Khoảng thời gian hiệu suất" là dữ liệu biến đổi của bạn:

df['PoP_Start']
df['PoP_End']
df['FY19_Start'] = '10/1/2018'
df['FY19_End'] = '09/30/2019'

Giả sử tất cả dữ liệu ở định dạng ngày giờ tức là -

df['FY19_Start'] = pd.to_datetime(df['FY19_Start'])
df['FY19_End'] = pd.to_datetime(df['FY19_End'])

Hãy thử các phương trình sau để tìm số ngày trùng lặp:

min1 = np.minimum(df['POP_End'], df['FY19_End'])
max2 = np.maximum(df['POP_Start'], df['FY19_Start'])

df['Overlap_2019'] = (min1 - max2) / np.timedelta64(1, 'D')
df['Overlap_2019'] = np.maximum(df['Overlap_2019']+1,0)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.