Отримайте різницю між датами в тижнях, місяцях, кварталах та роках


78

У мене є дві дати, скажемо 14.01.2013і 26.03.2014.

Я хотів би отримати різницю між цими двома датами з точки зору тижнів (?), Місяців (у прикладі 14), кварталів (4) та років (1).

Чи знаєте ви найкращий спосіб отримати це?


Протягом тижнів я знайшов такий час різниці (час1, час2, одиниці = "тижні"). На жаль, це не працює місяцями, кварталами, роками.
ddg

Відповіді:


76

як що до цього:

# get difference between dates `"01.12.2013"` and `"31.12.2013"`

# weeks
difftime(strptime("26.03.2014", format = "%d.%m.%Y"),
strptime("14.01.2013", format = "%d.%m.%Y"),units="weeks")
Time difference of 62.28571 weeks

# months
(as.yearmon(strptime("26.03.2014", format = "%d.%m.%Y"))-
as.yearmon(strptime("14.01.2013", format = "%d.%m.%Y")))*12
[1] 14

# quarters
(as.yearqtr(strptime("26.03.2014", format = "%d.%m.%Y"))-
as.yearqtr(strptime("14.01.2013", format = "%d.%m.%Y")))*4
[1] 4

# years
year(strptime("26.03.2014", format = "%d.%m.%Y"))-
year(strptime("14.01.2013", format = "%d.%m.%Y"))
[1] 1

as.yearmon()і as.yearqtr()є в упаковці zoo. year()є в упаковці lubridate. Як ти гадаєш?


12
Ця відповідь вимагає обережності ... він вважатиме, що 31 грудня 2013 року на 1 рік відрізняється від наступного дня, 1 січня 2014 р. Іноді цього вимагають, але часто ні.
Грегор Томас

1
Розширення застереження Грегора: yearце дасть різницю лише в календарних роках , тому, якщо вам потрібно знати різницю за деяку частку року, це не буде придатним.
Умаомамаомао

'format' + значення за замовчуванням можуть полегшити друк: difftime (format ("2014-03-26"), format ("2013-01-14"), units = "weeks") Різниця в часі 62.28571 тижнів
tim

51

Усі існуючі відповіді є недосконалими (IMO) і або роблять припущення щодо бажаного результату, або не забезпечують гнучкості для бажаного результату.

На основі прикладів з OP та заявлених очікуваних відповідей OP, я думаю, це саме ті відповіді, які ви шукаєте (плюс деякі додаткові приклади, які полегшують екстраполяцію).

(Для цього потрібна лише база R і не потрібен зоопарк або мастило)

Перетворити на об'єкти Datetime

date_strings = c("14.01.2013", "26.03.2014")
datetimes = strptime(date_strings, format = "%d.%m.%Y") # convert to datetime objects

Різниця в днях

Ви можете використовувати різницю за кілька днів, щоб отримати деякі з наших пізніших відповідей

diff_in_days = difftime(datetimes[2], datetimes[1], units = "days") # days
diff_in_days
#Time difference of 435.9583 days

Різниця в тижнях

Різниця в тижнях - це окремий випадок units = "weeks"вdifftime()

diff_in_weeks = difftime(datetimes[2], datetimes[1], units = "weeks") # weeks
diff_in_weeks
#Time difference of 62.27976 weeks

Зверніть увагу, що це те саме, що розділити наші diff_in_days на 7 (7 днів на тиждень)

as.double(diff_in_days)/7
#[1] 62.27976

Різниця в роках

Подібною логікою ми можемо виводити роки з diff_in_days

diff_in_years = as.double(diff_in_days)/365 # absolute years
diff_in_years
#[1] 1.194406

Ви, мабуть, очікуєте, що різниця в роках буде "1", тому я припускаю, що ви просто хочете порахувати абсолютні календарні роки або щось інше, що ви можете легко зробити за допомогою floor()

# get desired output, given your definition of 'years'
floor(diff_in_years)
#[1] 1

Різниця в кварталах

# get desired output for quarters, given your definition of 'quarters'
floor(diff_in_years * 4)
#[1] 4

Різниця в місяцях

Можна обчислити це як конвертацію з diff_years

# months, defined as absolute calendar months (this might be what you want, given your question details)
months_diff = diff_in_years*12
floor(month_diff)
#[1] 14

Я знаю, що це питання застаріле, але враховуючи, що мені все-таки довелося вирішити цю проблему, я думав, що додам свої відповіді. Сподіваюся, це допоможе.


Я думаю, це не спрацьовує, коли months_diff<0
timat

@timat, чи можете ви навести конкретний приклад двох рядків дат, для яких це не працює для вас?
rysqui

1
date_strings = c("14.07.2014", "10.03.2015")дати -4замість цього 7 місяців згідно з першим визначенням ..
тимат

@timat ти маєш рацію! Я не впевнений, чому коли я писав це, я не просто обчислював місяці безпосередньо з того diff_in_years, наприклад, у вашому прикладі, правдивою відповіддю є те, що минуло майже 8 повних місяців. Ви отримуєте правильну відповідь, просто коли diff_in_years*12 = 7.857534я виправив свою відповідь - дякую.
rysqui

2
Пам’ятайте, коли ви ділите дні 365на одержання років, це стосується лише 3 з 4 років через високосний рік. Поділ на 365.25було б більш точним, особливо для обчислення віку.
MS Berends

14

Тижнями ви можете використовувати функцію difftime:

date1 <- strptime("14.01.2013", format="%d.%m.%Y")
date2 <- strptime("26.03.2014", format="%d.%m.%Y")
difftime(date2,date1,units="weeks")
Time difference of 62.28571 weeks

Але difftimeне працює з тривалістю протягом тижнів.
Нижче наведено дуже неоптимальне рішення, яке використовується cut.POSIXtдля цих тривалістей, але ви можете обійти це:

seq1 <- seq(date1,date2, by="days")
nlevels(cut(seq1,"months"))
15
nlevels(cut(seq1,"quarters"))
5
nlevels(cut(seq1,"years"))
2

Однак це кількість місяців, кварталів або років, що охоплюються вашим часовим інтервалом, а не тривалість вашого часового інтервалу, виражена в місяцях, кварталах, роках (оскільки вони не мають постійної тривалості). Беручи до уваги коментар, який ви зробили щодо відповіді @SvenHohenstein, я вважаю, що ви можете використати nlevels(cut(seq1,"months")) - 1те, чого ви намагаєтесь досягти.


14

Я просто написав це для іншого питання, а потім наткнувся тут.

library(lubridate)

#' Calculate age
#' 
#' By default, calculates the typical "age in years", with a
#' \code{floor} applied so that you are, e.g., 5 years old from
#' 5th birthday through the day before your 6th birthday. Set
#' \code{floor = FALSE} to return decimal ages, and change \code{units}
#' for units other than years.
#' @param dob date-of-birth, the day to start calculating age.
#' @param age.day the date on which age is to be calculated.
#' @param units unit to measure age in. Defaults to \code{"years"}. Passed to \link{\code{duration}}.
#' @param floor boolean for whether or not to floor the result. Defaults to \code{TRUE}.
#' @return Age in \code{units}. Will be an integer if \code{floor = TRUE}.
#' @examples
#' my.dob <- as.Date('1983-10-20')
#' age(my.dob)
#' age(my.dob, units = "minutes")
#' age(my.dob, floor = FALSE)
age <- function(dob, age.day = today(), units = "years", floor = TRUE) {
    calc.age = interval(dob, age.day) / duration(num = 1, units = units)
    if (floor) return(as.integer(floor(calc.age)))
    return(calc.age)
}

Приклади використання:

my.dob <- as.Date('1983-10-20')

age(my.dob)
# [1] 31

age(my.dob, floor = FALSE)
# [1] 31.15616

age(my.dob, units = "minutes")
# [1] 16375680

age(seq(my.dob, length.out = 6, by = "years"))
# [1] 31 30 29 28 27 26

'new_interval' застарілий; використовуйте замість цього "інтервал". Не підтримується у версії '1.5.0'.
Маной Кумар,

Я просто написав це для іншого запитання, а потім наткнувся тут. Я також! Невелика порада: використовуйте фігурні дужки після оператора if: if (floor) { ... }і використовуйте лише returnтоді, коли ви повертаєте щось на половині своєї функції. Останній рядок повинен просто бути calc.age.
MS Berends

@MSBerends Це лише вказівки щодо стилю. Я набагато віддаю перевагу явно returnзі своїми функціями - я вважаю це зрозумілішим. Звичайно, у власному коді використовуйте стиль, який вам підходить.
Грегор Томас

Дуже правильно. Щодо функції: вона не працює в даному випадку: 1950-01-17і 2015-01-01. Повертається 65, але цій людині не виповнилося б 65 років до 17.01.2015 ... Будь-яка ідея, чому?
MS Berends

Це дивно! Я розгляну це ще. Проблема, здається, 2013 рік, якщо ви визначитеся, yy = seq.Date(from = as.Date("2010-01-01"), to = as.Date("2015-01-01"), by = "year")а потім спробуєте age(dob = as.Date("1950-01-17"), age.day = yy), результат перескакує понад 62. І лише в тому випадку, якщо DOB знаходиться між 1949 і 1952 роками. Дуже дивно ...
Грегор Томас

5

Ось рішення:

dates <- c("14.01.2013", "26.03.2014")

# Date format:
dates2 <- strptime(dates, format = "%d.%m.%Y")

dif <- diff(as.numeric(dates2)) # difference in seconds

dif/(60 * 60 * 24 * 7) # weeks
[1] 62.28571
dif/(60 * 60 * 24 * 30) # months
[1] 14.53333
dif/(60 * 60 * 24 * 30 * 3) # quartes
[1] 4.844444
dif/(60 * 60 * 24 * 365) # years
[1] 1.194521

Дякуємо за це, однак ваше рішення буде працювати не у всіх випадках. Наприклад, якщо взяти дати <- c ("01.12.2013", "31.12.2013"), ви отримаєте різницю в місяцях = 1, тоді як я очікував, що різниця буде 0 (обидві дати трапляються на 13 грудня).
ddg

3
Хоча я все ще ніжно точний, я пропоную використовувати 365.242 для кількості днів у році замість 365.
CousinCocaine

4

Тут ще бракує lubridateвідповіді (хоча функція Грегора побудована на цьому пакеті)

Документація про часовий інтервал змащення дуже корисна для розуміння різниці між періодами та тривалістю. Мені також подобається масляний шпаргалка і ця дуже корисна тема

library(lubridate)

dates <- c(dmy('14.01.2013'), dmy('26.03.2014'))

span <- dates[1] %--% dates[2] #creating an interval object

#creating period objects 
as.period(span, unit = 'year') 
#> [1] "1y 2m 12d 0H 0M 0S"
as.period(span, unit = 'month')
#> [1] "14m 12d 0H 0M 0S"
as.period(span, unit = 'day')
#> [1] "436d 0H 0M 0S"

Періоди не приймають тижні як одиниці. Але ви можете перевести тривалість у тижні:

as.duration(span)/ dweeks(1)
#makes duration object (in seconds) and divides by duration of a week (in seconds)
#> [1] 62.28571

Створено 04.11.2019 пакетом reprex (v0.3.0)


1

спробуйте це протягом місяця рішення

StartDate <- strptime("14 January 2013", "%d %B %Y") 
EventDates <- strptime(c("26 March 2014"), "%d %B %Y") 
difftime(EventDates, StartDate) 

Привіт Рейчел, дякую за це, однак це не працює. Коли я запускаю strptime ("14 січня 2013", "% d% B% Y"), я отримую NA.
ddg

Те саме тут .. Якщо я використовую цей крок, я отримую
НС

Це рішення працюватиме лише для англійських мов. Набагато безпечніше використовувати %mчислові місяці (наприклад, 1 січня) замість %B.
Frank Schmitt

1

Більш "точний" розрахунок. Тобто число тижня / місяця / кварталу / року для неповного тижня / місяця / кварталу / року є часткою календарних днів у цьому тижні / місяці / кварталі / році. Наприклад, кількість місяців між 22.02.2016 та 31.03.2016 дорівнює 8/29 + 31/31 = 1,27586

пояснення в рядку з кодом

#' Calculate precise number of periods between 2 dates
#' 
#' @details The number of week/month/quarter/year for a non-complete week/month/quarter/year 
#'     is the fraction of calendar days in that week/month/quarter/year. 
#'     For example, the number of months between 2016-02-22 and 2016-03-31 
#'     is 8/29 + 31/31 = 1.27586
#' 
#' @param startdate start Date of the interval
#' @param enddate end Date of the interval
#' @param period character. It must be one of 'day', 'week', 'month', 'quarter' and 'year'
#' 
#' @examples 
#' identical(numPeriods(as.Date("2016-02-15"), as.Date("2016-03-31"), "month"), 15/29 + 1)
#' identical(numPeriods(as.Date("2016-02-15"), as.Date("2016-03-31"), "quarter"), (15 + 31)/(31 + 29 + 31))
#' identical(numPeriods(as.Date("2016-02-15"), as.Date("2016-03-31"), "year"), (15 + 31)/366)
#' 
#' @return exact number of periods between
#' 
numPeriods <- function(startdate, enddate, period) {

    numdays <- as.numeric(enddate - startdate) + 1
    if (grepl("day", period, ignore.case=TRUE)) {
        return(numdays)

    } else if (grepl("week", period, ignore.case=TRUE)) {
        return(numdays / 7)
    }

    #create a sequence of dates between start and end dates
    effDaysinBins <- cut(seq(startdate, enddate, by="1 day"), period)

    #use the earliest start date of the previous bins and create a breaks of periodic dates with
    #user's period interval
    intervals <- seq(from=as.Date(min(levels(effDaysinBins)), "%Y-%m-%d"), 
        by=paste("1",period), 
        length.out=length(levels(effDaysinBins))+1)

    #create a sequence of dates between the earliest interval date and last date of the interval
    #that contains the enddate
    allDays <- seq(from=intervals[1],
        to=intervals[intervals > enddate][1] - 1,
        by="1 day")

    #bin all days in the whole period using previous breaks
    allDaysInBins <- cut(allDays, intervals)

    #calculate ratio of effective days to all days in whole period
    sum( tabulate(effDaysinBins) / tabulate(allDaysInBins) )
} #numPeriods

Будь ласка, повідомте мене, якщо ви знайдете більше граничних випадків, коли вищевказане рішення не працює.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.