Ermitteln Sie den Unterschied zwischen Daten in Wochen, Monaten, Quartalen und Jahren

77

Ich habe zwei Termine, sagen wir mal 14.01.2013und 26.03.2014.

Ich möchte die Differenz zwischen diesen beiden Daten in Wochen (?), Monaten (im Beispiel 14), Quartalen (4) und Jahren (1) ermitteln.

Kennen Sie den besten Weg, um dies zu bekommen?

ddg
quelle
Für die Wochen fand ich die folgende Differenz (Zeit1, Zeit2, Einheiten = "Wochen"). Dies funktioniert leider seit Monaten, Quartalen, Jahren nicht mehr.
ddg

Antworten:

76

was ist damit:

# 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()und as.yearqtr()sind im Paket zoo. year()ist im Paket lubridate. Was denken Sie?

ddg
quelle
12
Diese Antwort erfordert Vorsicht. Der 31. Dezember 2013 wird als ein Jahr anders angesehen als der nächste Tag, der 1. Januar 2014. Manchmal ist dies erwünscht, aber oft nicht.
Gregor Thomas
1
Erweiterung von Gregors Vorbehalt: Gibt nuryear in Kalenderjahren einen Unterschied. Wenn Sie also den Unterschied in einem Bruchteil eines Jahres kennen müssen, ist er nicht geeignet.
Umaomamaomao
'Format' + Standardeinstellungen können die Eingabe vereinfachen: Difftime (Format ("2014-03-26"), Format ("2013-01-14"), Einheiten = "Wochen") Zeitunterschied von 62.28571 Wochen
Tim
51

Alle vorhandenen Antworten sind unvollständig (IMO) und treffen entweder Annahmen über die gewünschte Ausgabe oder bieten keine Flexibilität für die gewünschte Ausgabe.

Basierend auf den Beispielen aus dem OP und den angegebenen erwarteten Antworten des OP denke ich, dass dies die Antworten sind, nach denen Sie suchen (plus einige zusätzliche Beispiele, die die Extrapolation erleichtern).

(Dies erfordert nur Basis R und erfordert keinen Zoo oder Schmiermittel)

In Datetime-Objekte konvertieren

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

Unterschied in Tagen

Sie können den Diff in Tagen verwenden, um einige unserer späteren Antworten zu erhalten

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

Unterschied in Wochen

Der Unterschied in Wochen ist ein Sonderfall von units = "weeks"indifftime()

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

Beachten Sie, dass dies dem Teilen unserer diff_in_days durch 7 (7 Tage in einer Woche) entspricht.

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

Unterschied in Jahren

Mit ähnlicher Logik können wir Jahre aus diff_in_days ableiten

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

Sie scheinen zu erwarten, dass der Unterschied in Jahren "1" ist, also gehe ich davon aus, dass Sie nur absolute Kalenderjahre oder etwas anderes zählen möchten, was Sie einfach mit verwenden können floor()

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

Unterschied in Quartalen

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

Unterschied in Monaten

Kann dies als Umrechnung von diff_years berechnen

# 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

Ich weiß, dass diese Frage alt ist, aber da ich dieses Problem gerade noch lösen musste, dachte ich, ich würde meine Antworten hinzufügen. Ich hoffe es hilft.

rysqui
quelle
Ich denke, das funktioniert nicht, wenn der months_diff<0 ist.
Timat
@timat Kannst du ein konkretes Beispiel für zwei Datumszeichenfolgen geben, für die dies bei dir nicht funktioniert?
Rysqui
1
date_strings = c("14.07.2014", "10.03.2015")Geben Sie -4stattdessen 7 Monate gemäß der ersten Definition.
Timat
@ Timim du hast recht! Ich bin mir nicht sicher, warum ich beim Schreiben nicht nur Monate direkt aus dem berechnet habe diff_in_years, z. B. in Ihrem Beispiel ist die wahre Antwort, dass fast 8 volle Monate vergangen sind. Sie erhalten die richtige Antwort, indem diff_in_years*12 = 7.857534ich meine Antwort korrigiere - danke.
Rysqui
2
Denken Sie daran, wenn Sie Tage durch 365Jahre teilen , um Jahre zu erhalten, gilt dies aufgrund von Schaltjahren nur für 3 von 4 Jahren. Die Division durch 365.25wäre genauer, insbesondere für die Berechnung eines Alters.
MS Berends
14

Wochenlang können Sie folgende Funktionen verwenden 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

Funktioniert aber difftimenicht mit Dauer über Wochen.
Das Folgende ist eine sehr suboptimale Lösung cut.POSIXtfür diese Zeiträume, aber Sie können sie umgehen:

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

Dies ist jedoch die Anzahl der Monate, Quartale oder Jahre, die sich über Ihr Zeitintervall erstrecken, und nicht die Dauer Ihres Zeitintervalls, ausgedrückt in Monaten, Quartalen, Jahren (da diese keine konstante Dauer haben). In Anbetracht des Kommentars, den Sie zu @SvenHohenstein abgegeben haben, würde ich denken, dass Sie ihn nlevels(cut(seq1,"months")) - 1für das verwenden können, was Sie erreichen möchten .

Planapus
quelle
14

Ich habe das gerade für eine andere Frage geschrieben und bin dann hierher gestolpert.

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)
}

Anwendungsbeispiele:

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
Gregor Thomas
quelle
'new_interval' ist veraltet; Verwenden Sie stattdessen 'Intervall'. In Version '1.5.0' veraltet.
Manoj Kumar
Ich habe das gerade für eine andere Frage geschrieben und bin dann hierher gestolpert. Ich auch! Kleiner Rat: Verwenden Sie geschweifte Klammern nach einer if-Anweisung: if (floor) { ... }und nur, returnwenn Sie etwas zur Hälfte Ihrer Funktion zurückgeben. Die letzte Zeile sollte einfach sein calc.age.
MS Berends
@MSBerends Das sind nur Stilrichtlinien. Ich bevorzuge es explizit returnmit meinen Funktionen - ich finde es klarer. Verwenden Sie in Ihrem eigenen Code natürlich den Stil, der zu Ihnen passt.
Gregor Thomas
Sehr richtig. In Bezug auf die Funktion: es funktioniert in diesem Fall nicht richtig: 1950-01-17und 2015-01-01. Es kehrt zurück 65, aber diese Person würde nicht vor dem 17.01.2015 65 Jahre alt werden ... Irgendeine Idee warum?
MS Berends
Das ist seltsam! Ich werde es genauer untersuchen. Das Problem scheint 2013 zu sein, wenn Sie definieren yy = seq.Date(from = as.Date("2010-01-01"), to = as.Date("2015-01-01"), by = "year")und dann versuchen age(dob = as.Date("1950-01-17"), age.day = yy), springt das Ergebnis über 62. Und nur wenn das Geburtsdatum zwischen 1949 und 1952 liegt. Sehr seltsam ...
Gregor Thomas
5

Hier ist eine Lösung:

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
Sven Hohenstein
quelle
Vielen Dank dafür, aber Ihre Lösung wird nicht in allen Fällen funktionieren. Wenn Sie beispielsweise Daten <- c ("01.12.2013", "31.12.2013") verwenden, erhalten Sie eine Differenz in Monaten = 1, während ich erwarten würde, dass die Differenz 0 beträgt (beide Daten treten am 13. Dezember auf).
ddg
3
Obwohl immer noch nicht genau, schlage ich vor, 365,242 für die Anzahl der Tage in einem Jahr anstelle von 365 zu verwenden.
CousinCocaine
4

Termine sind hier ein großes Thema und die gegebenen Antworten sind großartig. Hier die noch ausstehende lubridateAntwort (obwohl die Funktion von @Gregor dieses Paket verwendet)

Die Dokumentation zur Schmierzeitspanne ist sehr hilfreich, um den Unterschied zwischen Zeiträumen und Dauer zu verstehen. Ich mag auch das Lubridate Cheatsheet und diesen sehr nützlichen Thread

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"

Perioden akzeptieren keine Wochen als Einheiten. Sie können die Dauer jedoch in Wochen umrechnen :

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

Erstellt am 04.11.2019 durch das reprex-Paket (v0.3.0)

Tjebo
quelle
1

Versuchen Sie dies für eine monatelange Lösung

StartDate <- strptime("14 January 2013", "%d %B %Y") 
EventDates <- strptime(c("26 March 2014"), "%d %B %Y") 
difftime(EventDates, StartDate) 
Rachel Gallen
quelle
Hallo Rachel, danke dafür, aber das funktioniert nicht. Wenn ich strptime ("14. Januar 2013", "% d% B% Y") starte, erhalte ich NA.
ddg
Gleiches hier .. Wenn ich diesen Schritt benutze,
bekomme
Diese Lösung funktioniert nur für englische Gebietsschemas. Es ist viel sicherer, %mnumerische Monate (z. B. 1 für Januar) anstelle von zu verwenden %B.
Frank Schmitt
1

Eine "genauere" Berechnung. Das heißt, die Anzahl der Woche / Monat / Quartal / Jahr für eine nicht vollständige Woche / Monat / Quartal / Jahr ist der Bruchteil der Kalendertage in dieser Woche / Monat / Quartal / Jahr. Beispielsweise beträgt die Anzahl der Monate zwischen dem 22.02.2016 und dem 31.03.2016 8/29 + 31/31 = 1,27586

Erklärung inline mit Code

#' 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

Bitte lassen Sie mich wissen, wenn Sie weitere Grenzfälle finden, in denen die oben genannte Lösung nicht funktioniert.

chinsoon12
quelle