17  Tarih ve saatler

17.1 Giriş

Bu bölüm R’da tarih ve saatlerle nasıl çalışacağınızı gösterecek. İlk bakışta tarih ve saatler basit görünür. Günlük yaşamınızda onları sürekli kullanırsınız ve pek kafa karışıklığına yol açmıyor gibidirler. Ancak tarih ve saatler hakkında daha fazla şey öğrendikçe giderek karmaşıklaştıklarını görürsünüz!

Başlangıç olarak bir yılda kaç gün, bir günde kaç saat olduğunu düşünün. Muhtemelen çoğu yılın 365, artık yılların ise 366 gün olduğunu hatırladınız. Bir yılın artık yıl olup olmadığını belirleyen kuralın tamamını biliyor musunuz1? Bir gündeki saat sayısı biraz daha belirsizdir: Çoğu gün 24 saattir ancak yaz saati uygulamasını kullanan yerlerde her yıl bir gün 23, başka bir gün ise 25 saattir.

Tarih ve saatler zordur; çünkü iki fiziksel olguyu (Dünya’nın kendi ekseni etrafındaki dönüşü ve Güneş çevresindeki yörüngesi) aylar, saat dilimleri ve yaz saati uygulaması dahil bir dizi jeopolitik olguyla uzlaştırmak zorundadır. Bu bölüm tarih ve saatlerle ilgili son ayrıntıya kadar her şeyi öğretmeyecek ancak yaygın veri analizi sorunlarında işinize yarayacak uygulamalı beceriler için sağlam bir temel sunacaktır.

Çeşitli girdilerden tarih-saat oluşturmayı ve ardından yıl, ay ve gün gibi bileşenleri çıkarmayı göstererek başlayacağız. Sonra yapmak istediğiniz işleme göre farklı türleri bulunan zaman aralıklarıyla çalışmanın zorlu ayrıntılarına gireceğiz. Bölümü saat dilimlerinin doğurduğu ek güçlükleri kısaca ele alarak bitireceğiz.

17.1.1 Ön koşullar

Bu bölüm R’da tarih ve saatlerle çalışmayı kolaylaştıran lubridate paketine odaklanacak. lubridate, tidyverse’in güncel sürümü itibarıyla temel tidyverse’in bir parçasıdır. Alıştırma verileri için nycflights13 paketine de ihtiyacımız olacak.

17.2 Tarih/saat oluşturma

Zamandaki bir ana başvuran üç tarih/saat verisi türü vardır:

  • Bir tarih. Tibbles bunu <date> olarak yazdırır.

  • Bir gün içindeki saat. Tibbles bunu <time> olarak yazdırır.

  • Tarih-saat, tarih ile saatin birleşimidir: Zamandaki bir anı benzersiz biçimde (genellikle en yakın saniyeye kadar) tanımlar. Tibbles bunu <dttm> olarak yazdırır. Temel R bunlara POSIXct der ama bu adın pek akılda kalıcı olduğu söylenemez.

R’ın saatleri saklamak için yerleşik bir sınıfı olmadığından bu bölümde tarihlere ve tarih-saatlere odaklanacağız. Böyle bir sınıfa ihtiyacınız varsa hms paketini kullanabilirsiniz.

Her zaman ihtiyacınızı karşılayan en basit veri türünü kullanmalısınız. Bu, tarih-saat yerine tarih kullanabiliyorsanız tarihi tercih etmeniz gerektiği anlamına gelir. Saat dilimlerini ele alma zorunluluğu nedeniyle tarih-saatler çok daha karmaşıktır; bölümün sonunda bu konuya döneceğiz.

Geçerli tarih veya tarih-saati almak için today() ya da now() kullanabilirsiniz:

today()
#> [1] "2026-08-23"
now()
#> [1] "2026-08-23 19:47:36 UTC"

Aşağıdaki bölümlerde tarih/saat oluşturmak için kullanacağınız dört yöntem açıklanmaktadır:

  • readr ile dosya okurken.
  • Bir karakter dizisinden.
  • Ayrı tarih-saat bileşenlerinden.
  • Mevcut bir tarih/saat nesnesinden.

17.2.1 İçe aktarma sırasında

CSV dosyanız ISO8601 tarih veya tarih-saat içeriyorsa hiçbir şey yapmanız gerekmez; readr bunu otomatik olarak tanır:

csv <- "
  date,datetime
  2022-01-02,2022-01-02 05:12
"
read_csv(csv)
#> Warning: The `file` argument of `read_csv()` should use `I()` for literal data as of
#> readr 2.2.0.
#>   
#>   # Bad (for example):
#>   read_csv("x,y\n1,2")
#>   
#>   # Good:
#>   read_csv(I("x,y\n1,2"))
#> # A tibble: 1 × 2
#>   date       datetime           
#>   <date>     <dttm>             
#> 1 2022-01-02 2022-01-02 05:12:00

ISO8601 standardını daha önce duymadıysanız bu, tarih bileşenlerinin büyükten küçüğe sıralanıp - ile ayrıldığı uluslararası bir tarih yazım standardıdır2. Örneğin 3 Mayıs 2022, ISO8601’de 2022-05-03 olarak yazılır. ISO8601 tarihleri saat de içerebilir; saat, dakika ve saniye : ile, tarih ve saat bileşenleri ise T ya da boşlukla ayrılır. Örneğin 3 Mayıs 2022 saat 16.26’yı 2022-05-03 16:26 veya 2022-05-03T16:26 olarak yazabilirsiniz.

Diğer tarih-saat biçimlerinde col_types ile birlikte col_date() veya col_datetime() ve bir tarih-saat biçimi kullanmanız gerekir. readr’ın kullandığı tarih-saat biçimi birçok programlama dilinde kullanılan bir standarttır ve tarih bileşenini % ile onu izleyen tek bir karakterle tanımlar. Örneğin %Y-%m-%d; yıl, -, sayı olarak ay, - ve günden oluşan bir tarihi belirtir. Tablo 17.1 tüm seçenekleri listeler.

Tablo 17.1: readr’ın anladığı tüm tarih biçimleri
Tür Kod Anlam Örnek
Yıl %Y 4 basamaklı yıl 2021
%y 2 basamaklı yıl 21
Ay %m Sayı 2
%b Kısaltılmış ad Feb
%B Tam ad February
Gün %d Bir veya iki basamak 2
%e İki basamak 02
Saat %H 24 saatlik düzende saat 13
%I 12 saatlik düzende saat 1
%p AM/PM pm
%M Dakika 35
%S Saniye 45
%OS Ondalık bileşenli saniye 45.35
%Z Saat dilimi adı America/Chicago
%z UTC’den fark +0800
Diğer %. Rakam olmayan bir öğeyi atla :
%* Rakam olmayan tüm öğeleri atla

Bu kod ise oldukça belirsiz bir tarihe uygulanan birkaç seçeneği gösterir:

csv <- "
  date
  01/02/15
"

read_csv(csv, col_types = cols(date = col_date("%m/%d/%y")))
#> # A tibble: 1 × 1
#>   date      
#>   <date>    
#> 1 2015-01-02

read_csv(csv, col_types = cols(date = col_date("%d/%m/%y")))
#> # A tibble: 1 × 1
#>   date      
#>   <date>    
#> 1 2015-02-01

read_csv(csv, col_types = cols(date = col_date("%y/%m/%d")))
#> # A tibble: 1 × 1
#>   date      
#>   <date>    
#> 1 2001-02-15

Tarih biçimini nasıl belirtirseniz belirtin, tarih R’a içe aktarıldıktan sonra her zaman aynı biçimde görüntülenir.

%b veya %B kullanıyor ve İngilizce dışındaki tarihlerle çalışıyorsanız bir locale() da sağlamanız gerekir. date_names_langs() içindeki yerleşik diller listesine bakın ya da date_names() ile kendinizinkini oluşturun.

17.2.2 Karakter dizilerinden

Tarih-saat belirtim dili güçlüdür ancak tarih biçiminin dikkatle incelenmesini gerektirir. Alternatif bir yaklaşım, bileşenlerin sırasını belirttiğinizde biçimi otomatik olarak saptamaya çalışan lubridate yardımcılarını kullanmaktır. Bunları kullanmak için tarihlerinizde yıl, ay ve günün hangi sırada yer aldığını belirleyip “y”, “m” ve “d” harflerini aynı sıraya koyun. Bu size tarihinizi ayrıştıracak lubridate fonksiyonunun adını verir. Örneğin:

ymd("2017-01-31")
#> [1] "2017-01-31"
mdy("January 31st, 2017")
#> [1] "2017-01-31"
dmy("31-Jan-2017")
#> [1] "2017-01-31"

ymd() ve benzeri fonksiyonlar tarih oluşturur. Tarih-saat oluşturmak için ayrıştırma fonksiyonunun adına alt çizgi ve “h”, “m”, “s” harflerinden birini ya da birkaçını ekleyin:

ymd_hms("2017-01-31 20:11:59")
#> [1] "2017-01-31 20:11:59 UTC"
mdy_hm("01/31/2017 08:01")
#> [1] "2017-01-31 08:01:00 UTC"

Bir saat dilimi sağlayarak tarihten tarih-saat oluşturulmasını da zorunlu kılabilirsiniz:

ymd("2017-01-31", tz = "UTC")
#> [1] "2017-01-31 UTC"

Burada GMT ya da Greenwich Ortalama Saati olarak da bilebileceğiniz, 0° boylamındaki saat olan UTC3 saat dilimini kullanıyorum4. UTC yaz saati uygulamasını kullanmadığından onunla hesaplama yapmak biraz daha kolaydır.

17.2.3 Ayrı bileşenlerden

Bazen tek bir karakter dizisi yerine tarih-saatin ayrı bileşenleri birden çok sütuna dağılmış olur. flights verisinde durum böyledir:

flights |> 
  select(year, month, day, hour, minute)
#> # A tibble: 336,776 × 5
#>    year month   day  hour minute
#>   <int> <int> <int> <dbl>  <dbl>
#> 1  2013     1     1     5     15
#> 2  2013     1     1     5     29
#> 3  2013     1     1     5     40
#> 4  2013     1     1     5     45
#> 5  2013     1     1     6      0
#> 6  2013     1     1     5     58
#> # ℹ 336,770 more rows

Bu tür bir girdiden tarih/saat oluşturmak için tarihlerde make_date(), tarih-saatlerde make_datetime() kullanın:

flights |> 
  select(year, month, day, hour, minute) |> 
  mutate(departure = make_datetime(year, month, day, hour, minute))
#> # A tibble: 336,776 × 6
#>    year month   day  hour minute departure          
#>   <int> <int> <int> <dbl>  <dbl> <dttm>             
#> 1  2013     1     1     5     15 2013-01-01 05:15:00
#> 2  2013     1     1     5     29 2013-01-01 05:29:00
#> 3  2013     1     1     5     40 2013-01-01 05:40:00
#> 4  2013     1     1     5     45 2013-01-01 05:45:00
#> 5  2013     1     1     6      0 2013-01-01 06:00:00
#> 6  2013     1     1     5     58 2013-01-01 05:58:00
#> # ℹ 336,770 more rows

Aynı işlemi flights içindeki dört saat sütununun her biri için yapalım. Saatler biraz sıra dışı bir biçimde gösterildiğinden saat ve dakika bileşenlerini çıkarmak için modüler aritmetik kullanıyoruz. Tarih-saat değişkenlerini oluşturduktan sonra bölümün geri kalanında inceleyeceğimiz değişkenlere odaklanıyoruz.

make_datetime_100 <- function(year, month, day, time) {
  make_datetime(year, month, day, time %/% 100, time %% 100)
}

flights_dt <- flights |> 
  filter(!is.na(dep_time), !is.na(arr_time)) |> 
  mutate(
    dep_time = make_datetime_100(year, month, day, dep_time),
    arr_time = make_datetime_100(year, month, day, arr_time),
    sched_dep_time = make_datetime_100(year, month, day, sched_dep_time),
    sched_arr_time = make_datetime_100(year, month, day, sched_arr_time)
  ) |> 
  select(origin, dest, ends_with("delay"), ends_with("time"))

flights_dt
#> # A tibble: 328,063 × 9
#>   origin dest  dep_delay arr_delay dep_time            sched_dep_time     
#>   <chr>  <chr>     <dbl>     <dbl> <dttm>              <dttm>             
#> 1 EWR    IAH           2        11 2013-01-01 05:17:00 2013-01-01 05:15:00
#> 2 LGA    IAH           4        20 2013-01-01 05:33:00 2013-01-01 05:29:00
#> 3 JFK    MIA           2        33 2013-01-01 05:42:00 2013-01-01 05:40:00
#> 4 JFK    BQN          -1       -18 2013-01-01 05:44:00 2013-01-01 05:45:00
#> 5 LGA    ATL          -6       -25 2013-01-01 05:54:00 2013-01-01 06:00:00
#> 6 EWR    ORD          -4        12 2013-01-01 05:54:00 2013-01-01 05:58:00
#> # ℹ 328,057 more rows
#> # ℹ 3 more variables: arr_time <dttm>, sched_arr_time <dttm>, …

Bu verilerle kalkış saatlerinin yıl içindeki dağılımını görselleştirebiliriz:

flights_dt |> 
  ggplot(aes(x = dep_time)) + 
  geom_freqpoly(binwidth = 86400) # 86400 saniye = 1 gün

X ekseninde kalkış zamanı (Ocak-Aralık 2013), y ekseninde uçuş sayısı (0-1000) bulunan bir sıklık poligonu. Sıklık poligonu güne göre aralıklara ayrıldığından uçuşların günlük zaman serisi görülür. Haftalık örüntü baskındır; hafta sonlarında daha az uçuş vardır. Şubat başı, Temmuz başı, Kasım sonu ve Aralık sonunda şaşırtıcı derecede az uçuş bulunan birkaç gün öne çıkar.

Ya da tek bir gün içinde:

flights_dt |> 
  filter(dep_time < ymd(20130102)) |> 
  ggplot(aes(x = dep_time)) + 
  geom_freqpoly(binwidth = 600) # 600 saniye = 10 dakika

X ekseninde kalkış saati (1 Ocak 06.00-gece yarısı), y ekseninde uçuş sayısı (0-17) bulunan ve 10 dakikalık aralıklara bölünmüş bir sıklık poligonu. Yüksek değişkenlik nedeniyle belirgin bir örüntü görmek zordur; ancak çoğu aralıkta 8-12 uçuş vardır ve 06.00 öncesi ile 20.00 sonrasında belirgin biçimde daha az uçuş bulunur.

Tarih-saatleri sayısal bir bağlamda (örneğin histogramda) kullandığınızda 1’in 1 saniye anlamına geldiğine dikkat edin; dolayısıyla 86400’lük aralık genişliği bir gün demektir. Tarihler için 1, bir gün anlamına gelir.

17.2.4 Diğer türlerden

Tarih-saat ile tarih arasında geçiş yapmak isteyebilirsiniz. Bu, as_datetime() ve as_date() fonksiyonlarının görevidir:

as_datetime(today())
#> [1] "2026-08-23 UTC"
as_date(now())
#> [1] "2026-08-23"

Bazen tarih/saatleri “Unix başlangıcı” olan 1970-01-01’den sayısal farklar biçiminde alırsınız. Fark saniye cinsindeyse as_datetime(), gün cinsindeyse as_date() kullanın.

as_datetime(60 * 60 * 10)
#> [1] "1970-01-01 10:00:00 UTC"
as_date(365 * 10 + 2)
#> [1] "1980-01-01"

17.2.5 Alıştırmalar

  1. Geçersiz tarihler içeren bir karakter dizisini ayrıştırırsanız ne olur?

    ymd(c("2010-10-10", "bananas"))
  2. today() fonksiyonunun tzone argümanı ne yapar? Neden önemlidir?

  3. Aşağıdaki tarih-saatlerin her birini readr sütun belirtimi ve lubridate fonksiyonuyla nasıl ayrıştıracağınızı gösterin.

    d1 <- "January 1, 2010"
    d2 <- "2015-Mar-07"
    d3 <- "06-Jun-2017"
    d4 <- c("August 19 (2015)", "July 1 (2015)")
    d5 <- "12/30/14" # 30 Aralık 2014
    t1 <- "1705"
    t2 <- "11:15:10.12 PM"

17.3 Tarih-saat bileşenleri

Tarih-saat verilerini R’ın tarih-saat veri yapılarına nasıl içe aktaracağınızı öğrendiğinize göre bunlarla neler yapabileceğinizi inceleyelim. Bu bölüm ayrı bileşenleri alıp ayarlamanızı sağlayan erişim fonksiyonlarına odaklanacak. Sonraki bölümde tarih-saatlerle aritmetiğin nasıl çalıştığına bakacağız.

17.3.1 Bileşenleri alma

Tarihin ayrı bölümlerini year(), month(), mday() (ayın günü), yday() (yılın günü), wday() (haftanın günü), hour(), minute() ve second() erişim fonksiyonlarıyla çıkarabilirsiniz. Bunlar temelde make_datetime() fonksiyonunun tersidir.

datetime <- ymd_hms("2026-07-08 12:34:56")

year(datetime)
#> [1] 2026
month(datetime)
#> [1] 7
mday(datetime)
#> [1] 8

yday(datetime)
#> [1] 189
wday(datetime)
#> [1] 4

month() ve wday() fonksiyonlarında ayın veya haftanın gününün kısaltılmış adını döndürmek için label = TRUE ayarını kullanabilirsiniz. Tam adı döndürmek için abbr = FALSE ayarını kullanın.

month(datetime, label = TRUE)
#> [1] Jul
#> 12 Levels: Jan < Feb < Mar < Apr < May < Jun < Jul < Aug < Sep < ... < Dec
wday(datetime, label = TRUE, abbr = FALSE)
#> [1] Wednesday
#> 7 Levels: Sunday < Monday < Tuesday < Wednesday < Thursday < ... < Saturday

Hafta içi hafta sonuna göre daha fazla uçuş kalktığını görmek için wday() kullanabiliriz:

flights_dt |> 
  mutate(wday = wday(dep_time, label = TRUE)) |> 
  ggplot(aes(x = wday)) +
  geom_bar()

X ekseninde haftanın günleri, y ekseninde uçuş sayısı bulunan bir çubuk grafik. Pazartesiden cumaya uçuş sayısı yaklaşık aynıdır (~48.000) ve hafta boyunca hafifçe azalır. Pazar biraz daha düşük (~45.000), cumartesi ise çok daha düşüktür (~38.000).

Saat içindeki dakikaya göre ortalama kalkış gecikmesine de bakabiliriz. İlginç bir örüntü vardır: 20-30 ve 50-60. dakikalar arasında kalkan uçuşların gecikmeleri saatin geri kalanına göre çok daha düşüktür!

flights_dt |> 
  mutate(minute = minute(dep_time)) |> 
  group_by(minute) |> 
  summarize(
    avg_delay = mean(dep_delay, na.rm = TRUE),
    n = n()
  ) |> 
  ggplot(aes(x = minute, y = avg_delay)) +
  geom_line()

X ekseninde gerçek kalkış dakikası (0-60), y ekseninde ortalama gecikme (4-20) bulunan bir çizgi grafiği. Ortalama gecikme (0, 12) noktasında başlar, düzenli biçimde (18, 20)'ye yükselir, sonra hızla düşerek saatin yaklaşık 23. dakikasında 9 dakikalık en düşük gecikmeye ulaşır. Ardından yeniden (17, 35)'e yükselip hızla (55, 4)'e düşer ve (60, 9)'a yükselerek biter.

İlginç biçimde planlanan kalkış saatine baktığımızda bu kadar güçlü bir örüntü görmeyiz:

sched_dep <- flights_dt |> 
  mutate(minute = minute(sched_dep_time)) |> 
  group_by(minute) |> 
  summarize(
    avg_delay = mean(arr_delay, na.rm = TRUE),
    n = n()
  )

ggplot(sched_dep, aes(x = minute, y = avg_delay)) +
  geom_line()

X ekseninde planlanan kalkış dakikası (0-60), y ekseninde ortalama gecikme (4-16) bulunan bir çizgi grafiği. Belirgin bir örüntü yoktur; yalnızca ortalama gecikmenin saat boyunca yaklaşık 10 dakikadan 8 dakikaya düştüğüne ilişkin zayıf bir işaret vardır.

Öyleyse gerçek kalkış saatlerinde bu örüntüyü neden görüyoruz? İnsanlar tarafından toplanan birçok veride olduğu gibi Şekil 17.1 üzerinde görüldüğü üzere uçuşların “yuvarlak” saatlerde kalkmasına yönelik güçlü bir eğilim vardır. İnsan yargısı içeren verilerle çalışırken bu tür örüntülere karşı her zaman dikkatli olun!

X ekseninde kalkış dakikası (0-60), y ekseninde uçuş sayısı (0-60.000) bulunan bir çizgi grafiği. Uçuşların çoğunun tam saatte (~60.000) veya buçukta (~35.000) kalkması planlanmıştır. Diğer uçuşların neredeyse tamamı beşin katlarında, biraz fazlası ise 15, 45 ve 55. dakikalarda planlanmıştır.
Şekil 17.1: Her saatte kalkması planlanan uçuşların sayısını gösteren bir sıklık poligonu. 0 ve 30 gibi yuvarlak sayılara, genel olarak da beşin katlarına yönelik güçlü bir tercih görülür.

17.3.2 Yuvarlama

Ayrı bileşenleri çizmenin alternatif bir yolu, floor_date(), round_date() ve ceiling_date() ile tarihi yakındaki bir zaman birimine yuvarlamaktır. Her fonksiyon, ayarlanacak tarih vektörünü ve ardından aşağı, yukarı veya en yakına yuvarlanacak birimin adını alır. Örneğin bu yöntem haftalık uçuş sayısını çizmemizi sağlar:

flights_dt |> 
  count(week = floor_date(dep_time, "week")) |> 
  ggplot(aes(x = week, y = n)) +
  geom_line() + 
  geom_point()

X ekseninde hafta (Ocak-Aralık 2013), y ekseninde uçuş sayısı (2.000-7.000) bulunan bir çizgi grafiği. Şubattan kasıma kadar örüntü haftada yaklaşık 7.000 uçuşla oldukça düzdür. Yılın ilk (~4.500 uçuş) ve son haftalarında (~2.500 uçuş) çok daha az uçuş vardır.

dep_time ile günün en erken anı arasındaki farkı hesaplayarak uçuşların gün içindeki dağılımını göstermek için yuvarlamayı kullanabilirsiniz:

flights_dt |> 
  mutate(dep_hour = dep_time - floor_date(dep_time, "day")) |> 
  ggplot(aes(x = dep_hour)) +
  geom_freqpoly(binwidth = 60 * 30)
#> Don't know how to automatically pick scale for object of type <difftime>.
#> Defaulting to continuous.

X ekseninde kalkış saati bulunan bir çizgi grafiği. Değerler gece yarısından sonraki saniye cinsinden olduğundan yorumlamak zordur.

Bir tarih-saat çifti arasındaki farkı hesaplamak bir difftime üretir (Bölüm 17.4.3 bölümünde daha ayrıntılı ele alınacaktır). Daha yararlı bir x ekseni elde etmek için bunu bir hms nesnesine dönüştürebiliriz:

flights_dt |> 
  mutate(dep_hour = hms::as_hms(dep_time - floor_date(dep_time, "day"))) |> 
  ggplot(aes(x = dep_hour)) +
  geom_freqpoly(binwidth = 60 * 30)

X ekseninde kalkış saati (gece yarısından gece yarısına), y ekseninde uçuş sayısı (0-15.000) bulunan bir çizgi grafiği. 05.00'ten önce çok az (<100) uçuş vardır. Uçuş sayısı hızla saatte 12.000'e yükselip 09.00'da 15.000 ile zirve yapar; 10.00-14.00 arasında saatte yaklaşık 8.000'e düşer. Ardından 20.00'ye kadar saatte yaklaşık 12.000'e yükselir ve yeniden hızla azalır.

17.3.3 Bileşenleri değiştirme

Her erişim fonksiyonunu tarih/saat bileşenlerini değiştirmek için de kullanabilirsiniz. Bu, veri analizinde sık gerekmez ancak açıkça hatalı tarihler içeren verileri temizlerken yararlı olabilir.

(datetime <- ymd_hms("2026-07-08 12:34:56"))
#> [1] "2026-07-08 12:34:56 UTC"

year(datetime) <- 2030
datetime
#> [1] "2030-07-08 12:34:56 UTC"
month(datetime) <- 01
datetime
#> [1] "2030-01-08 12:34:56 UTC"
hour(datetime) <- hour(datetime) + 1
datetime
#> [1] "2030-01-08 13:34:56 UTC"

Alternatif olarak mevcut bir değişkeni değiştirmek yerine update() ile yeni bir tarih-saat oluşturabilirsiniz. Bu fonksiyon birden çok değeri tek adımda ayarlamanızı da sağlar:

update(datetime, year = 2030, month = 2, mday = 2, hour = 2)
#> [1] "2030-02-02 02:34:56 UTC"

Değerler fazla büyükse sonraki birime devreder:

update(ymd("2023-02-01"), mday = 30)
#> [1] "2023-03-02"
update(ymd("2023-02-01"), hour = 400)
#> [1] "2023-02-17 16:00:00 UTC"

17.3.4 Alıştırmalar

  1. Bir gün içindeki uçuş saatlerinin dağılımı yıl boyunca nasıl değişir?

  2. dep_time, sched_dep_time ve dep_delay değişkenlerini karşılaştırın. Birbirleriyle tutarlı mı? Bulgularınızı açıklayın.

  3. air_time ile kalkış ve varış arasındaki süreyi karşılaştırın. Bulgularınızı açıklayın. (İpucu: Havaalanının konumunu göz önünde bulundurun.)

  4. Ortalama gecikme süresi gün boyunca nasıl değişir? dep_time mı yoksa sched_dep_time mı kullanmalısınız? Neden?

  5. Gecikme olasılığını en aza indirmek istiyorsanız haftanın hangi günü yola çıkmalısınız?

  6. diamonds$carat ile flights$sched_dep_time dağılımlarını benzer kılan nedir?

  7. 20-30 ve 50-60. dakikalar arasındaki erken kalkışların, planlanandan erken kalkan uçuşlardan kaynaklandığı hipotezimizi doğrulayın. İpucu: Bir uçuşun gecikip gecikmediğini belirten ikili bir değişken oluşturun.

17.4 Zaman aralıkları

Sırada çıkarma, toplama ve bölme dahil tarihlerle aritmetiğin nasıl çalıştığını öğreneceksiniz. Bu sırada zaman aralıklarını temsil eden üç önemli sınıfı tanıyacaksınız:

  • Kesin bir saniye sayısını temsil eden süreler (durations).
  • Hafta ve ay gibi insani birimleri temsil eden dönemler (periods).
  • Bir başlangıç ve bitiş noktasını temsil eden aralıklar (intervals).

Süre, dönem ve aralık arasında nasıl seçim yaparsınız? Her zamanki gibi probleminizi çözen en basit veri yapısını seçin. Yalnızca fiziksel zamanla ilgileniyorsanız süreyi; insani zaman birimleri eklemeniz gerekiyorsa dönemi; bir zaman aralığının insani birimlerle ne kadar uzun olduğunu bulmanız gerekiyorsa aralığı kullanın.

17.4.1 Süreler

R’da iki tarihi çıkardığınızda bir difftime nesnesi elde edersiniz:

# Hadley kaç yaşında?
h_age <- today() - ymd("1979-10-14")
h_age
#> Time difference of 17115 days

difftime sınıfı nesnesi saniye, dakika, saat, gün veya hafta cinsinden bir zaman aralığını kaydeder. Bu belirsizlik difftime nesneleriyle çalışmayı biraz zorlaştırabilir; bu nedenle lubridate her zaman saniye kullanan bir alternatif sağlar: süre.

as.duration(h_age)
#> [1] "1478736000s (~46.86 years)"

Süreler çeşitli kullanışlı oluşturucu fonksiyonlarla birlikte gelir:

dseconds(15)
#> [1] "15s"
dminutes(10)
#> [1] "600s (~10 minutes)"
dhours(c(12, 24))
#> [1] "43200s (~12 hours)" "86400s (~1 days)"
ddays(0:5)
#> [1] "0s"                "86400s (~1 days)"  "172800s (~2 days)"
#> [4] "259200s (~3 days)" "345600s (~4 days)" "432000s (~5 days)"
dweeks(3)
#> [1] "1814400s (~3 weeks)"
dyears(1)
#> [1] "31557600s (~1 years)"

Süreler zaman aralığını her zaman saniye cinsinden kaydeder. Daha büyük birimler dakika, saat, gün, hafta ve yılların saniyeye dönüştürülmesiyle oluşturulur: Bir dakikada 60 saniye, bir saatte 60 dakika, bir günde 24 saat ve bir haftada 7 gün vardır. Daha büyük zaman birimleri daha sorunludur. Bir yıl, yıl başına “ortalama” gün sayısını, yani 365,25’i kullanır. Değişkenlik çok fazla olduğu için bir ayı süreye dönüştürmenin yolu yoktur.

Süreleri toplayabilir ve çarpabilirsiniz:

2 * dyears(1)
#> [1] "63115200s (~2 years)"
dyears(1) + dweeks(12) + dhours(15)
#> [1] "38869200s (~1.23 years)"

Tarihlere süre ekleyebilir ve tarihlerden süre çıkarabilirsiniz:

tomorrow <- today() + ddays(1)
last_year <- today() - dyears(1)

Ancak süreler kesin bir saniye sayısını temsil ettiğinden bazen beklenmedik bir sonuç alabilirsiniz:

one_am <- ymd_hms("2026-03-08 01:00:00", tz = "America/New_York")

one_am
#> [1] "2026-03-08 01:00:00 EST"
one_am + ddays(1)
#> [1] "2026-03-09 02:00:00 EDT"

8 Mart saat 01.00’den bir gün sonrası neden 9 Mart saat 02.00’dir? Tarihe dikkatle bakarsanız saat diliminin de değiştiğini fark edebilirsiniz. Yaz saati uygulaması 8 Mart’ta başladığı için o gün yalnızca 23 saattir; dolayısıyla tam bir güne karşılık gelen saniyeyi eklediğimizde farklı bir saate ulaşırız.

17.4.2 Dönemler

Bu sorunu çözmek için lubridate dönemleri sağlar. Dönemler zaman aralıklarıdır ancak saniye cinsinden sabit uzunlukları yoktur; bunun yerine gün ve ay gibi “insani” zaman birimleriyle çalışırlar. Bu sayede daha sezgisel davranabilirler:

one_am
#> [1] "2026-03-08 01:00:00 EST"
one_am + days(1)
#> [1] "2026-03-09 01:00:00 EDT"

Süreler gibi dönemler de çeşitli kullanışlı oluşturucu fonksiyonlarla oluşturulabilir.

hours(c(12, 24))
#> [1] "12H 0M 0S" "24H 0M 0S"
days(7)
#> [1] "7d 0H 0M 0S"
months(1:6)
#> [1] "1m 0d 0H 0M 0S" "2m 0d 0H 0M 0S" "3m 0d 0H 0M 0S" "4m 0d 0H 0M 0S"
#> [5] "5m 0d 0H 0M 0S" "6m 0d 0H 0M 0S"

Dönemleri toplayabilir ve çarpabilirsiniz:

10 * (months(6) + days(1))
#> [1] "60m 10d 0H 0M 0S"
days(50) + hours(25) + minutes(2)
#> [1] "50d 25H 2M 0S"

Elbette dönemleri tarihlere de ekleyebilirsiniz. Dönemlerin beklediğiniz sonucu verme olasılığı sürelere göre daha yüksektir:

# Artık yıl
ymd("2024-01-01") + dyears(1)
#> [1] "2024-12-31 06:00:00 UTC"
ymd("2024-01-01") + years(1)
#> [1] "2025-01-01"

# Yaz saati uygulaması
one_am + ddays(1)
#> [1] "2026-03-09 02:00:00 EDT"
one_am + days(1)
#> [1] "2026-03-09 01:00:00 EDT"

Uçuş tarihlerimizle ilgili bir tuhaflığı düzeltmek için dönemleri kullanalım. Bazı uçaklar New York’tan kalkmadan önce varış noktalarına ulaşmış gibi görünmektedir.

flights_dt |> 
  filter(arr_time < dep_time) 
#> # A tibble: 10,633 × 9
#>   origin dest  dep_delay arr_delay dep_time            sched_dep_time     
#>   <chr>  <chr>     <dbl>     <dbl> <dttm>              <dttm>             
#> 1 EWR    BQN           9        -4 2013-01-01 19:29:00 2013-01-01 19:20:00
#> 2 JFK    DFW          59        NA 2013-01-01 19:39:00 2013-01-01 18:40:00
#> 3 EWR    TPA          -2         9 2013-01-01 20:58:00 2013-01-01 21:00:00
#> 4 EWR    SJU          -6       -12 2013-01-01 21:02:00 2013-01-01 21:08:00
#> 5 EWR    SFO          11       -14 2013-01-01 21:08:00 2013-01-01 20:57:00
#> 6 LGA    FLL         -10        -2 2013-01-01 21:20:00 2013-01-01 21:30:00
#> # ℹ 10,627 more rows
#> # ℹ 3 more variables: arr_time <dttm>, sched_arr_time <dttm>, …

Bunlar gece boyunca süren uçuşlardır. Hem kalkış hem de varış saatleri için aynı tarih bilgisini kullandık ancak bu uçuşlar ertesi gün varmıştır. Her gece uçuşunun varış saatine days(1) ekleyerek bunu düzeltebiliriz.

flights_dt <- flights_dt |> 
  mutate(
    overnight = arr_time < dep_time,
    arr_time = arr_time + days(overnight),
    sched_arr_time = sched_arr_time + days(overnight)
  )

Artık tüm uçuşlarımız fizik yasalarına uyuyor.

flights_dt |> 
  filter(arr_time < dep_time) 
#> # A tibble: 0 × 10
#> # ℹ 10 variables: origin <chr>, dest <chr>, dep_delay <dbl>,
#> #   arr_delay <dbl>, dep_time <dttm>, sched_dep_time <dttm>, …

17.4.3 Aralıklar

dyears(1) / ddays(365) ne döndürür? Tam olarak bir değildir; çünkü dyears() ortalama bir yıldaki saniye sayısı, yani 365,25 gün olarak tanımlanır.

Peki years(1) / days(1) ne döndürür? Yıl 2015 ise 365, 2016 ise 366 döndürmelidir! lubridate’in tek ve kesin bir yanıt vermesi için yeterli bilgi yoktur. Bunun yerine bir tahmin verir:

years(1) / days(1)
#> [1] 365.25

Daha doğru bir ölçüm istiyorsanız bir aralık kullanmanız gerekir. Aralık, başlangıç ve bitiş tarih-saatlerinden oluşan bir çifttir; onu başlangıç noktası bulunan bir süre olarak da düşünebilirsiniz.

start %--% end yazarak bir aralık oluşturabilirsiniz:

y2023 <- ymd("2023-01-01") %--% ymd("2024-01-01")
y2024 <- ymd("2024-01-01") %--% ymd("2025-01-01")

y2023
#> [1] 2023-01-01 UTC--2024-01-01 UTC
y2024
#> [1] 2024-01-01 UTC--2025-01-01 UTC

Ardından yılda kaç gün bulunduğunu öğrenmek için bunu days() değerine bölebilirsiniz:

y2023 / days(1)
#> [1] 365
y2024 / days(1)
#> [1] 366

17.4.4 Alıştırmalar

  1. days(!overnight) ve days(overnight) ifadelerini R öğrenmeye yeni başlamış birine açıklayın. Bilmeniz gereken temel gerçek nedir?

  2. 2015 yılındaki her ayın ilk gününü veren bir tarih vektörü oluşturun. Geçerli yıldaki her ayın ilk gününü veren bir tarih vektörü oluşturun.

  3. Doğum gününüzü tarih olarak alıp yaşınızı yıl cinsinden döndüren bir fonksiyon yazın.

17.5 Saat dilimleri

Saat dilimleri jeopolitik oluşumlarla etkileşimleri nedeniyle son derece karmaşık bir konudur. Neyse ki ayrıntıların tümü veri analizi için önemli olmadığından hepsine girmemiz gerekmiyor; ancak doğrudan ele almamız gereken birkaç güçlük var.

İlk güçlük, saat dilimlerinin günlük adlarının çoğunlukla belirsiz olmasıdır. Örneğin Amerikalıysanız EST’yi, yani Eastern Standard Time’ı muhtemelen biliyorsunuzdur. Ancak hem Avustralya hem de Kanada’da EST adlı saat dilimleri vardır! R, karışıklığı önlemek için uluslararası standart IANA saat dilimlerini kullanır. Bunlar genellikle {kıta}/{şehir} ya da {okyanus}/{şehir} biçimindeki tutarlı {bölge}/{konum} adlandırma düzenini kullanır. Örnekler arasında “America/New_York”, “Europe/Paris” ve “Pacific/Auckland” bulunur.

Saat dilimlerini genellikle bir ülke veya ülke içindeki bölgeyle ilişkilendirdiğiniz hâlde adlarda neden şehir kullanıldığını merak edebilirsiniz. Bunun nedeni IANA veritabanının onlarca yıllık saat dilimi kurallarını kaydetmek zorunda olmasıdır. On yıllar içinde ülkeler oldukça sık ad değiştirir (veya bölünür) ancak şehir adları genellikle aynı kalır. Bir başka sorun, adın yalnızca güncel davranışı değil bütün tarihçeyi yansıtması gerektiğidir. Örneğin hem “America/New_York” hem de “America/Detroit” saat dilimleri vardır. İki şehir de günümüzde Eastern Standard Time’ı kullanır; ancak Detroit’in bulunduğu Michigan eyaleti 1969-1972 yıllarında yaz saati uygulamasını izlemediğinden farklı bir ada ihtiyaç duyar. Bu öykülerden bazılarını okumak için ham saat dilimi veritabanına (https://www.iana.org/time-zones) göz atmaya değer!

R’ın geçerli saat diliminizin ne olduğunu düşündüğünü Sys.timezone() ile öğrenebilirsiniz:

Sys.timezone()
#> [1] "UTC"

(R bilmiyorsa NA alırsınız.)

Tüm saat dilimi adlarının eksiksiz listesini OlsonNames() ile görebilirsiniz:

length(OlsonNames())
#> [1] 598
head(OlsonNames())
#> [1] "Africa/Abidjan"     "Africa/Accra"       "Africa/Addis_Ababa"
#> [4] "Africa/Algiers"     "Africa/Asmara"      "Africa/Asmera"

R’da saat dilimi, tarih-saatin yalnızca yazdırılmasını denetleyen bir niteliğidir. Örneğin şu üç nesne zamandaki aynı anı temsil eder:

x1 <- ymd_hms("2024-06-01 12:00:00", tz = "America/New_York")
x1
#> [1] "2024-06-01 12:00:00 EDT"

x2 <- ymd_hms("2024-06-01 18:00:00", tz = "Europe/Copenhagen")
x2
#> [1] "2024-06-01 18:00:00 CEST"

x3 <- ymd_hms("2024-06-02 04:00:00", tz = "Pacific/Auckland")
x3
#> [1] "2024-06-02 04:00:00 NZST"

Çıkarma işlemiyle aynı an olduklarını doğrulayabilirsiniz:

x1 - x2
#> Time difference of 0 secs
x1 - x3
#> Time difference of 0 secs

Aksi belirtilmedikçe lubridate her zaman UTC kullanır. UTC (Coordinated Universal Time), bilim topluluğunun kullandığı standart saat dilimidir ve yaklaşık olarak GMT’ye (Greenwich Mean Time) eşdeğerdir. Yaz saati uygulaması olmadığı için hesaplamalarda kullanışlı bir gösterimdir. c() gibi tarih-saatleri birleştiren işlemler çoğu zaman saat dilimini kaldırır. Bu durumda tarih-saatler ilk öğenin saat diliminde görüntülenir:

x4 <- c(x1, x2, x3)
x4
#> [1] "2024-06-01 12:00:00 EDT" "2024-06-01 12:00:00 EDT"
#> [3] "2024-06-01 12:00:00 EDT"

Saat dilimini iki şekilde değiştirebilirsiniz:

  • Zamandaki anı aynı tutup görüntülenme biçimini değiştirin. An doğru olduğunda ancak daha doğal bir gösterim istediğinizde bunu kullanın.

    x4a <- with_tz(x4, tzone = "Australia/Lord_Howe")
    x4a
    #> [1] "2024-06-02 02:30:00 +1030" "2024-06-02 02:30:00 +1030"
    #> [3] "2024-06-02 02:30:00 +1030"
    x4a - x4
    #> Time differences in secs
    #> [1] 0 0 0

    (Bu aynı zamanda saat dilimlerinin başka bir güçlüğünü gösterir: Saat farklarının tümü tam sayı değildir!)

  • Altta yatan zaman anını değiştirin. Yanlış saat dilimiyle etiketlenmiş bir anınız olduğunda ve bunu düzeltmeniz gerektiğinde kullanın.

    x4b <- force_tz(x4, tzone = "Australia/Lord_Howe")
    x4b
    #> [1] "2024-06-01 12:00:00 +1030" "2024-06-01 12:00:00 +1030"
    #> [3] "2024-06-01 12:00:00 +1030"
    x4b - x4
    #> Time differences in hours
    #> [1] -14.5 -14.5 -14.5

17.6 Özet

Bu bölüm tarih-saat verileriyle çalışmanıza yardımcı olmak için lubridate’in sunduğu araçları tanıttı. Tarih ve saatlerle çalışmak gereğinden zor görünebilir; ancak umarız bu bölüm bunun nedenini görmenizi sağlamıştır: Tarih-saatler ilk bakışta göründüğünden daha karmaşıktır ve olası her durumu ele almak bu karmaşıklığı artırır. Verileriniz hiçbir zaman yaz saati sınırını aşmasa veya artık yıl içermese bile fonksiyonların bu durumları ele alabilmesi gerekir.

Bir sonraki bölüm eksik değerleri topluca ele alacak. Onları birkaç yerde gördünüz ve kendi analizlerinizde de kuşkusuz karşılaştınız; artık eksik değerleri ele almak için kullanışlı tekniklerden oluşan bir araç çantası sunmanın zamanı geldi.


  1. Bir yıl 4’e tam bölünüyorsa artık yıldır; 100’e de tam bölünüyorsa değildir, ancak 400’e de tam bölünüyorsa yine artık yıldır. Başka bir deyişle her 400 yıllık dönemde 97 artık yıl vardır.↩︎

  2. https://xkcd.com/1179/↩︎

  3. UTC’nin açılımını merak edebilirsiniz. Bu kısaltma İngilizce “Coordinated Universal Time” ile Fransızca “Temps Universel Coordonné” arasında bir uzlaşmadır.↩︎

  4. Boylam sistemini hangi ülkenin geliştirdiğini tahmin edene ödül yok.↩︎