17 التواريخ والأوقات
17.1 مقدمة
سيعرض لك هذا الفصل كيفية التعامل مع التواريخ والأوقات في لغة R. للوهلة الأولى، تبدو التواريخ والأوقات بسيطة. فأنت تستخدمها طوال الوقت في حياتك اليومية، ولا يبدو أنها تسبب الكثير من الإرباك. ومع ذلك، كلما تعلمت المزيد عن التواريخ والأوقات، كلما بدت أكثر تعقيداً!
كنوع من الإحماء، فكر في عدد الأيام الموجودة في السنة، وعدد الساعات في اليوم. ربما تتذكر أن معظم السنوات تحتوي على 365 يوماً، لكن السنوات الكبيسة تحتوي على 366 يوماً. هل تعرف القاعدة الكاملة لتحديد ما إذا كانت السنة كبيسة أم لا1؟ أما عدد الساعات في اليوم فأقل وضوحاً: تحتوي معظم الأيام على 24 ساعة، ولكن في الأماكن التي تستخدم التوقيت الصيفي (DST)، يحتوي يوم واحد كل عام على 23 ساعة ويوم آخر على 25 ساعة.
التواريخ والأوقات صعبة لأنها تتطلب التوفيق بين ظاهرتين فيزيائيتين (دوران الأرض حول محورها ومدارها حول الشمس) مع مجموعة كاملة من الظواهر الجيوسياسية بما في ذلك الأشهر، المناطق الزمنية، والتوقيت الصيفي. لن يعلمك هذا الفصل كل التفاصيل الدقيقة عن التواريخ والأوقات، ولكنه سيمنحك أساساً متيناً من المهارات العملية التي ستساعدك في مواجهة تحديات تحليل البيانات الشائعة.
سَنستهلُّ العرض ببيان كيفية إنشاء كائنات التاريخ والوقت (date-times) من مُدخلات مُتنوّعة، وتوضيح كيفية استخراج مُكوّناتها كالعام والشهر واليوم بمجرد تكوينها. بَعْدها، سننتقل إلى تفصيل المسألة الدقيقة المُتعلّقة بالتعامل مع الفترات الزمنية (time spans)، والتي تتخذ أشكالاً (أو تصنيفات) عِدّة تَبَعاً للغرض المرجوّ من تحليلها. وفي الختام، سنستعرض مُناقشة موجزة حول التحدّيات الإضافية التي تطرحها النطاقات الزمنية (time zones).
17.1.1 المتطلبات المسبقة
سيركز هذا الفصل على حزمة lubridate، والتي تجعل العمل مع التواريخ والأوقات في R أكثر سهولة. اعتباراً من أحدث إصدار لـ tidyverse، أصبحت lubridate جزءاً أساسياً منها. سنحتاج أيضاً إلى حزمة nycflights13 للتدرب على البيانات.
17.2 إنشاء التواريخ/الأوقات
هناك ثلاثة أنواع من بيانات التاريخ/الوقت التي تشير إلى لحظة زمنية معينة:
تاريخ (Date). تطبعه إطارات البيانات (Tibbles) بالشكل
<date>.وقت (Time) خلال اليوم. تطبعه إطارات البيانات بالشكل
<time>.تاريخ-وقت (Date-time) وهو تاريخ بالإضافة إلى وقت: حيث يحدد لحظة زمنية فريدة (عادةً لأقرب ثانية). تطبعه إطارات البيانات بالشكل
<dttm>. تطلق عليه لغة R الأساسية (Base R) اسم POSIXct، لكن هذا الاسم ليس سهلاً في النطق.
في هذا الفصل سنركز على التواريخ والتواريخ-الأوقات لأن R لا تحتوي على فئة برمجية مدمجة لخزن الأوقات بمفردها. إذا كنت بحاجة إلى واحدة، يمكنك استخدام حزمة hms.
يجب عليك دائماً استخدام أبسط نوع بيانات ممكن لتلبية احتياجاتك. هذا يعني أنه إذا كان بإمكانك استخدام “تاريخ” بدلاً من “تاريخ-وقت”، فيجب عليك فعل ذلك. تعد التواريخ-الأوقات أكثر تعقيداً بشكل كبير بسبب الحاجة إلى التعامل مع المناطق الزمنية، والتي سنعود إليها في نهاية الفصل.
للحصول على التاريخ الحالي أو التاريخ والوقت الحاليين، يمكنك استخدام today() أو now():
بخلاف ذلك، تصف الأقسام التالية الطرق الأربع التي من المحتمل أن تستخدمها لإنشاء تاريخ/وقت:
- أثناء قراءة ملف باستخدام readr.
- من سلسلة نصية (string).
- من مكونات منفردة للتاريخ والوقت.
- من كائن تاريخ/وقت حالي.
17.2.1 أثناء الاستيراد
إذا كان ملف CSV الخاص بك يحتوي على تاريخ أو تاريخ-وقت بتنسيق ISO8601، فلن تحتاج إلى فعل أي شيء؛ ستتعرف عليه حزمة readr تلقائياً:
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 من قبل، فهو معيار دولي2 لكتابة التواريخ حيث تُنظم مكونات التاريخ من الأكبر إلى الأصغر تفصيلاً وبينها مفصل -. على سبيل المثال، في ISO8601 يكتب 3 مايو 2022 بالشكل 2022-05-03. يمكن أن تتضمن تواريخ ISO8601 أوقاتاً أيضاً، حيث يفصل بين الساعة والدقيقة والثانية باستخدام :، ويتم الفصل بين مكونات التاريخ والوقت إما بـ T أو بمسافة. على سبيل المثال، يمكنك كتابة الساعة 4:26 مساءً في 3 مايو 2022 إما بالشكل 2022-05-03 16:26 أو 2022-05-03T16:26.
بالنسبة لتنسيقات التاريخ والوقت الأخرى، ستلاحظ حاجتك لاستخدام col_types بالإضافة إلى col_date() أو col_datetime() جنباً إلى جنب مع تنسيق التاريخ والوقت. تنسيق التاريخ والوقت المستخدم بواسطة readr هو معيار مستخدم عبر العديد من لغات البرمجة، حيث يصف مكون التاريخ باستخدام % متبوعاً بحرف واحد. على سبيل المثال، يحدد %Y-%m-%d تاريخاً يتكون من سنة، -، شهر (كرقم) -، يوم. يعرض الجدول 17.1 جميع الخيارات المتاحة.
| النوع | الرمز | المعنى | مثال |
|---|---|---|---|
| السنة | %Y |
سنة من 4 أرقام | 2021 |
%y |
سنة من رقمين | 21 | |
| الشهر | %m |
رقم | 2 |
%b |
الاسم المختصر | Feb | |
%B |
الاسم الكامل | February | |
| اليوم | %d |
رقم أو رقمين | 2 |
%e |
رقمين | 02 | |
| الوقت | %H |
ساعة بنظام 24 ساعة | 13 |
%I |
ساعة بنظام 12 ساعة | 1 | |
%p |
صباحاً/مساءً (AM/PM) | pm | |
%M |
دقائق | 35 | |
%S |
ثواني | 45 | |
%OS |
ثواني مع أجزاء عشرية | 45.35 | |
%Z |
اسم المنطقة الزمنية | America/Chicago | |
%z |
الفارق الزمني عن UTC | +0800 | |
| أخرى | %. |
تخطي رمز واحد غير رقمي | : |
%* |
تخطي أي عدد من الرموز غير الرقمية |
ويعرض هذا الكود بضعة خيارات مطبقة على تاريخ غامض للغاية:
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لاحظ أنه بغض النظر عن كيفية تحديد تنسيق التاريخ، فإنه يُعرض دائماً بنفس الطريقة بمجرد إدخاله إلى R.
إذا كنت تستخدم %b أو %B وتعمل مع تواريخ بلغات غير الإنجليزية، فستحتاج أيضاً إلى توفير بيئة محليّة locale(). راجع قائمة اللغات المدمجة في date_names_langs()، أو أنشئ لغتك الخاصة بـ date_names().
17.2.2 من النصوص
لغة تحديد التاريخ والوقت قوية، ولكنها تتطلب تحليلاً دقيقاً لتنسيق التاريخ. هناك نهج بديل يتمثل في استخدام الدوال المساعدة لـ lubridate والتي تحاول تحديد التنسيق تلقائياً بمجرد تحديد ترتيب المكونات. لاستخدامها، حدد الترتيب الذي تظهر به السنة والشهر واليوم في تواريخك، ثم رتب “y” و “m” و “d” بنفس الترتيب. يعطيك هذا اسم دالة lubridate التي ستقوم بتحليل تاريخك. على سبيل المثال:
ymd() وأخواتها تقوم بإنشاء تواريخ. ولإنشاء تاريخ-وقت، أضف خطاً سفلياً (underscore) وواحداً أو أكثر من الأحرف “h” و “m” و “s” إلى اسم دالة التحليل:
يمكنك أيضاً إجبار إنشاء تاريخ-وقت من تاريخ ببساطة عن طريق تزويده بمنطقة زمنية:
ymd("2017-01-31", tz = "UTC")
#> [1] "2017-01-31 UTC"هنا استخدمت المنطقة الزمنية UTC3 والتي قد تعرفها أيضاً باسم GMT، أو توقيت غرينتش، وهو الوقت عند خط طول 0°4. وهي لا تستخدم التوقيت الصيفي، مما يجعل الحساب بها أسهل قليلاً.
17.2.3 من المكونات المنفردة
بدلاً من سلسلة نصية واحدة، ستكون لديك أحياناً المكونات المنفردة للتاريخ والوقت موزعة عبر أعمدة متعددة. هذا هو ما لدينا في بيانات flights:
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لإنشاء تاريخ/وقت من هذا النوع من المدخلات، استخدم make_date() للتواريخ، أو make_datetime() للتواريخ-الأوقات:
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دعنا نفعل الشيء نفسه لكل أعمدة الوقت الأربعة في flights. تم تمثيل الأوقات بتنسيق غريب قليلاً، لذا نستخدم حساب باقي القسمة (modulus arithmetic) لاستخراج مكونات الساعة والدقيقة. بمجرد إنشاء متغيرات التاريخ والوقت، نركز على المتغيرات التي سنستكشفها في بقية الفصل.
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>, …باستخدام هذه البيانات، يمكننا تصور توزيع أوقات المغادرة على مدار العام:
flights_dt |>
ggplot(aes(x = dep_time)) +
geom_freqpoly(binwidth = 86400) # 86400 seconds = 1 day
أو خلال يوم واحد:
flights_dt |>
filter(dep_time < ymd(20130102)) |>
ggplot(aes(x = dep_time)) +
geom_freqpoly(binwidth = 600) # 600 seconds = 10 minutes
لاحظ أنه عند استخدام التواريخ-الأوقات في سياق عددي (كما هو الحال في الرسم البياني التكراري)، فإن الرقم 1 يعني ثانية واحدة، لذا فإن اتساع الفئة (binwidth) البالغ 86400 يعني يوماً واحداً. أما بالنسبة للتواريخ، فإن 1 يعني يوماً واحداً.
17.2.4 من أنواع أخرى
قد ترغب في التحويل بين تاريخ-وقت وتاريخ. هذه هي وظيفة as_datetime() و as_date():
as_datetime(today())
#> [1] "2026-09-08 UTC"
as_date(now())
#> [1] "2026-09-08"في بعض الأحيان ستحصل على التواريخ/الأوقات كقيم عددية تزاح عن “حقبة يونكس” (Unix Epoch)، وهي 1970-01-01. إذا كانت إزاحة التوقيت بالثواني، استخدم as_datetime()؛ وإذا كانت بالأيام، استخدم as_date().
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 تمارين
- ماذا يحدث إذا قمت بتحليل سلسلة نصية تحتوي على تواريخ غير صالحة؟
ماذا يفعل المعامل
tzoneفي الدالةtoday()؟ لماذا هو مهم؟لكل من التواريخ والأوقات التالية، وضح كيف ستحدد تحليلها باستخدام مواصفات أعمدة readr ودالة من lubridate.
d1 <- "January 1, 2010"
d2 <- "2015-Mar-07"
d3 <- "06-Jun-2017"
d4 <- c("August 19 (2015)", "July 1 (2015)")
d5 <- "12/30/14" # Dec 30, 2014
t1 <- "1705"
t2 <- "11:15:10.12 PM"17.3 مكونات التاريخ والوقت
الآن بعد أن عرفت كيفية إدخال بيانات التاريخ والوقت في هياكل بيانات R، دعنا نستكشف ما يمكنك فعله بها. سيركز هذا القسم على دوال الوصول (accessor functions) التي تتيح لك الحصول على المكونات الفردية وتعديلها. بينما سينظر القسم التالي في كيفية عمل العمليات الحسابية مع التواريخ والأوقات.
17.3.1 الحصول على المكونات
يمكنك استخراج أجزاء منفردة من التاريخ باستخدام دوال الوصول مثل year() (السنة)، و month() (الشهر)، و mday() (يوم الشهر)، و yday() (يوم السنة)، و wday() (يوم الأسبوع)، و hour() (الساعة)، و minute() (الدقيقة)، و second() (الثانية). تعتبر هذه الدوال عملياً عكس دالة make_datetime().
بالنسبة للدالتين month() و wday()، يمكنك ضبط label = TRUE لإرجاع الاسم المختصر للشهر أو اليوم. وضبط abbr = FALSE لإرجاع الاسم الكامل.
يمكننا استخدام wday() لرؤية أن المزيد من الرحلات الجوية تغادر خلال أيام الأسبوع مقارنة بعطلة نهاية الأسبوع:
يمكننا أيضاً النظر في متوسط تأخير المغادرة حسب الدقيقة داخل الساعة. هناك نمط مثير للاهتمام: الرحلات المغادرة في الدقائق 20-30 و 50-60 لديها تأخيرات أقل بكثير من بقية الساعة!

ومن المثير للاهتمام أننا إذا نظرنا إلى وقت المغادرة المجدول، فلن نرى مثل هذا النمط القوي:

إذاً، لماذا نرى هذا النمط مع أوقات المغادرة الفعلية؟ حسناً، مثل معظم البيانات التي يجمعها البشر، هناك انحياز قوي نحو تسجيل مغادرة الرحلات الجوية في أوقات مغادرة “نموذجية أو دائرية”، كما يوضح الشكل 17.1. كن دائماً متنبهاً لهذا النوع من الأنماط كلما عملت مع بيانات تتضمن تقديراً بشرياً!
17.3.2 التقريب
هناك نهج بديل لرسم المكونات الفردية وهو تقريب التاريخ إلى وحدة زمنية قريبة، وذلك باستخدام floor_date()، و round_date()، و ceiling_date(). تأخذ كل دالة متجهاً من التواريخ لضبطه، ثم اسم الوحدة للتقريب للأدنى (floor)، أو للأعلى (ceiling)، أو للتقريب الأقرب. هذا يتلاءم، على سبيل المثال، مع رسم عدد الرحلات الجوية الأسبوعية:
flights_dt |>
count(week = floor_date(dep_time, "week")) |>
ggplot(aes(x = week, y = n)) +
geom_line() +
geom_point()
يمكنك استخدام التقريب لإظهار توزيع الرحلات على مدار اليوم عن طريق حساب الفرق بين dep_time وأبكر لحظة في ذلك اليوم:
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.
ينتج عن حساب الفرق بين زوج من التواريخ-الأوقات كائن من نوع difftime (المزيد حول ذلك في قسم 17.4.3). يمكننا تحويل ذلك إلى كائن hms للحصول على محور أفقي أكثر فائدة:
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)
17.3.3 تعديل المكونات
يمكنك أيضاً استخدام كل دالة من دوال الوصول لتعديل مكونات التاريخ/الوقت. لا يظهر هذا كثيراً في تحليل البيانات، ولكنه قد يكون مفيداً عند تنظيف البيانات التي تحتوي على تواريخ غير صحيحة بوضوح.
بدلاً من ذلك، وبدلاً من تعديل متغير موجود، يمكنك إنشاء تاريخ-وقت جديد باستخدام update(). يتيح لك هذا أيضاً تعيين قيم متعددة في خطوة واحدة:
update(datetime, year = 2030, month = 2, mday = 2, hour = 2)
#> [1] "2030-02-02 02:34:56 UTC"إذا كانت القيم كبيرة جداً، فسوف تتجاوز وتنتقل إلى المكون الأعلى (roll-over):
17.3.4 تمارين
كيف يتغير توزيع أوقات الرحلات الجوية خلال اليوم على مدار العام؟
قارن بين
dep_timeوsched_dep_timeوdep_delay. هل هي متسقة؟ اشرح ما وجدته.قارن بين
air_timeوالمدة الزمنية بين المغادرة والوصول. اشرح ما وجدته. (تلميح: ألقِ نظرة على موقع المطار.)كيف يتغير متوسط وقت التأخير على مدار اليوم؟ هل يجب عليك استخدام
dep_timeأمsched_dep_time؟ ولماذا؟ما هو يوم الأسبوع الذي يجب أن تغادر فيه إذا كنت تريد تقليل فرصة حدوث تأخير؟
ما الذي يجعل توزيع
diamonds$caratوflights$sched_dep_timeمتشابهاً؟تأكد من صحة فرضيتنا القائلة بأن المغادرة المبكرة للرحلات في الدقائق 20-30 و 50-60 ناتجة عن رحلات مجدولة غادرت مبكراً. تلميح: أنشئ متغيراً ثنائياً (binary variable) يخبرك ما إذا كانت الرحلة قد تأخرت أم لا.
17.4 الفترات الزمنية
بعد ذلك، ستتعلم كيف تعمل العمليات الحسابية مع التواريخ، بما في ذلك الطرح والجمع والقسمة. وفي الطريق، ستتعلم عن ثلاث فئات مهمة تمثل الفترات الزمنية:
- المدد (Durations)، والتي تمثل عدداً دقيقاً من الثواني.
- المُدد الشخصية/التقويمية (Periods)، والتي تمثل الوحدات البشرية مثل الأسابيع والأشهر.
- الفواصل الزمنية (Intervals)، والتي تمثل نقطة بداية ونهاية.
كيف تختار بين Duration و Period و Interval؟ كما هو الحال دائماً، اختر أبسط هيكل بيانات يحل مشكلتك. إذا كنت تهتم فقط بالوقت الفيزيائي، استخدم Duration؛ وإذا كنت بحاجة إلى إضافة أوقات بشرية، استخدم Period؛ وإذا كنت بحاجة إلى معرفة طول الفترة بالوحدات البشرية، استخدم Interval.
17.4.1 المدد (Durations)
في R، عندما تطرح تاريخين، تحصل على كائن من نوع difftime:
يسجل كائن فئة difftime فترة زمنية بالثواني، الدقائق، الساعات، الأيام، أو الأسابيع. هذا الغموض يمكن أن يجعل التعامل مع difftimes مزعجاً قليلاً، لذا توفر lubridate بديلاً يستخدم الثواني دائماً: duration.
as.duration(h_age)
#> [1] "1480118400s (~46.9 years)"تأتي المدد مع مجموعة من دوال الإنشاء المريحة:
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)"تسجل المدد دائماً الفترة الزمنية بالثواني. تُنشأ الوحدات الأكبر عن طريق تحويل الدقائق، الساعات، الأيام، الأسابيع، والسنوات إلى ثوانٍ: 60 ثانية في الدقيقة، 60 دقيقة في الساعة، 24 ساعة في اليوم، و7 أيام في الأسبوع. الوحدات الزمنية الأكبر أكثر إشكالية. تستخدم السنة عدد الأيام “المتوسط” في السنة، أي 365.25. لا توجد طريقة لتحويل شهر إلى مدة (duration) لأن هناك الكثير من التباين.
يمكنك جمع المدد ومضاعفتها:
يمكنك إضافة وطرح المدد من وإلى التواريخ:
ومع ذلك، لأن المدد تمثل عدداً دقيقاً من الثواني، قد تحصل أحياناً على نتيجة غير متوقعة:
لماذا أصبح اليوم التالي لـ 8 مارس الساعة 1 صباحاً هو 9 مارس الساعة 2 صباحاً؟ إذا نظرت بعناية إلى التاريخ، فقد تلاحظ أيضاً أن المناطق الزمنية قد تغيرت. يحتوي يوم 8 مارس على 23 ساعة فقط لأنه اليوم الذي يبدأ فيه التوقيت الصيفي (DST)، لذا إذا أضفنا ثواني يوم كامل، فسننتهي بوقت مختلف.
17.4.2 المُدد التقويمية (Periods)
لحل هذه المشكلة، توفر lubridate periods. الفترات (Periods) هي مدد زمنية ولكن ليس لها طول ثابت بالثواني، وبدلاً من ذلك تعمل مع الأوقات “البشرية”، مثل الأيام والأشهر. يتيح لها ذلك العمل بطريقة أكثر سهولة وبداهة:
one_am
#> [1] "2026-03-08 01:00:00 EST"
one_am + days(1)
#> [1] "2026-03-09 01:00:00 EDT"مثل المدد (durations)، يمكن إنشاء الفترات (periods) باستخدام عدد من دوال الإنشاء السهلة.
يمكنك جمع ومضاعفة الفترات:
وبالطبع، إضافتها إلى التواريخ. مقارنة بالمدد (durations)، من المرجح أن تفعل الفترات (periods) ما تتوقعه تماماً:
دعنا نستخدم الفترات لإصلاح أمر غريب يتعلق بتواريخ رحلاتنا الجوية. يبدو أن بعض الطائرات قد وصلت إلى وجهتها قبل مغادرتها من مدينة نيويورك.
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>, …هذه رحلات ليلية (تستمر عبر الليل). لقد استخدمنا نفس معلومات التاريخ لكل من أوقات المغادرة والوصول، لكن هذه الرحلات وصلت في اليوم التالي. يمكننا إصلاح ذلك عن طريق إضافة days(1) إلى وقت الوصول لكل رحلة ليلية.
الآن تخضع جميع رحلاتنا لقوانين الفيزياء.
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 الفواصل الزمنية
ماذا ترجع نتيجة dyears(1) / ddays(365)؟ إنها ليست واحداً تماماً، لأن dyears() مُعرَّفة بعدد الثواني في متوسط السنة، وهو 365.25 يوماً.
ماذا ترجع نتيجة years(1) / days(1)؟ حسناً، إذا كانت السنة 2015، فيجب أن ترجع 365، ولكن إذا كانت 2016، فيجب أن ترجع 366! لا توجد معلومات كافية لـ lubridate لإعطاء إجابة واحدة واضحة. ما تفعله بدلاً من ذلك هو إعطاء تقدير:
إذا كنت تريد قياساً أكثر دقة، فستحتاج إلى استخدام فصل زمني (interval). الفصل الزمني هو زوج من تاريخ ووقت البداية والنهاية، أو يمكنك التفكير فيه كمدة (duration) مع نقطة بداية.
يمكنك إنشاء فاصل زمني عن طريق كتابة start %--% end:
يمكنك بعد ذلك قسمته على days() لمعرفة عدد الأيام التي تناسب السنة:
17.4.4 تمارين
اشرح معنى
days(!overnight)وdays(overnight)لشخص بدأ للتو في تعلم R. ما هي الحقيقة الرئيسية التي تحتاج إلى معرفتها؟أنشئ متجهاً من التواريخ يعطي اليوم الأول من كل شهر في عام 2015. أنشئ متجهاً من التواريخ يعطي اليوم الأول من كل شهر في السنة الحالية.
اكتب دالة تتلقى تاريخ ميلادك (كتاريخ)، وترجع عمرك بالسنين.
17.5 المناطق الزمنية
المناطق الزمنية موضوع معقد للغاية بسبب تفاعلها مع الكيانات الجيوسياسية. لحسن الحظ، لا نحتاج إلى البحث في كل التفاصيل لأنها ليست كلها مهمة لتحليل البيانات، ولكن هناك بضعة تحديات سنحتاج إلى مواجهتها مباشرة.
التحدي الأول هو أن الأسماء اليومية للمناطق الزمنية تميل إلى أن تكون غامضة. على سبيل المثال، إذا كنت أمريكياً، فأنت على الأرجح معتاد على EST، أو التوقيت القياسي الشرقي. ومع ذلك، تمتلك كل من أستراليا وكندا أيضاً EST! لتجنب الارباك، تستخدم R مناطق IANA الزمنية المعيارية الدولية. تستخدم هذه الأسماء مخطط تسمية متسق وهو {area}/{location}، وعادة ما تكون بالشكل {continent}/{city} أو {ocean}/{city}. وتشمل الأمثلة “America/New_York” و “Europe/Paris” و “Pacific/Auckland”.
قد تتساءل لماذا تستخدم المنطقة الزمنية اسم مدينة، بينما تفكر عادة في المناطق الزمنية كشيء مرتبط بدولة أو منطقة داخل دولة. هذا لأن قاعدة بيانات IANA يجب أن تسجل قواعد المناطق الزمنية لعقود من الزمان. على مدار العقود، تتغير أسماء الدول (أو تتفكك) بشكل متكرر، لكن أسماء المدن تميل إلى البقاء كما هي. مشكلة أخرى هي أن الاسم يجب أن يعكس ليس فقط السلوك الحالي، ولكن أيضاً التاريخ الكامل. على سبيل المثال، هناك مناطق زمنية لكل من “America/New_York” و “America/Detroit”. تستخدم هاتان المدينتان حالياً التوقيت القياسي الشرقي، ولكن في 1969-1972، لم تتبع ميشيغان (الولاية التي تقع فيها ديترويت) التوقيت الصيفي، لذا كانت بحاجة إلى اسم مختلف. من المفيد قراءة قاعدة بيانات المناطق الزمنية الخام (المتاحة في https://www.iana.org/time-zones) لمجرد قراءة بعض هذه القصص!
يمكنك معرفة ما تعتقده R حول منطقتك الزمنية الحالية باستخدام Sys.timezone():
Sys.timezone()
#> [1] "UTC"(إذا كانت R لا تعرف، فستحصل على NA.)
ولرؤية القائمة الكاملة لجميع أسماء المناطق الزمنية استخدم OlsonNames():
length(OlsonNames())
#> [1] 598
head(OlsonNames())
#> [1] "Africa/Abidjan" "Africa/Accra" "Africa/Addis_Ababa"
#> [4] "Africa/Algiers" "Africa/Asmara" "Africa/Asmera"في R، المنطقة الزمنية هي خاصية (attribute) للتاريخ والوقت تتحكم فقط في طريقة الطباعة والعرض. على سبيل المثال، تمثل هذه الكائنات الثلاثة نفس اللحظة الزمنية:
يمكنك التحقق من أنها نفس الوقت باستخدام الطرح:
x1 - x2
#> Time difference of 0 secs
x1 - x3
#> Time difference of 0 secsما لم يُحدد خلاف ذلك، تستخدم lubridate دائماً UTC. UTC (التوقيت العالمي المنسق) هو المنطقة الزمنية القياسية المستخدمة من قبل المجتمع العلمي وتكافئ تقريباً GMT (توقيت غرينتش). وهي لا تحتوي على توقيت صيفي (DST)، مما يجعلها تمثيلاً مريحاً للحسابات. العمليات التي تجمع بين التواريخ والأوقات، مثل c()، غالباً ما تتجاهل المنطقة الزمنية. في هذه الحالة، ستُعرض التواريخ والأوقات في المنطقة الزمنية للعنصر الأول:
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"يمكنك تغيير المنطقة الزمنية بطريقتين:
- الحفاظ على اللحظة الزمنية كما هي، وتغيير طريقة عرضها فقط. استخدم هذا عندما تكون اللحظة صحيحة، ولكنك تريد عرضاً أكثر طبيعية.
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(يوضح هذا أيضاً تحدياً آخر للمناطق الزمنية: ليست كلها فروق ساعات صحيحة!)
- تغيير اللحظة الزمنية الأساسية نفسها. استخدم هذا عندما يكون لديك لحظة تم تسميتها بمنطقة زمنية غير صحيحة، وتحتاج إلى إصلاحها.
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.517.6 ملخص
قدم لك هذا الفصل الأدوات التي توفرها حزمة lubridate لمساعدتك في العمل مع بيانات التاريخ والوقت. قد يبدو العمل مع التواريخ والأوقات أصعب مما ينبغي، ولكن نأمل أن يكون هذا الفصل قد ساعدك في معرفة السبب — التواريخ والأوقات أكثر تعقيداً مما تبدو عليه للوهلة الأولى، ومعالجة كل موقف محتمل تضيف تعقيداً. حتى لو لم تتجاوز بياناتك أبداً حد التوقيت الصيفي أو تتضمن سنة كبيسة، يجب أن تكون الدوال قادرة على التعامل معها.
يقدم الفصل التالي مراجعة وشرحاً للقيم المفقودة. لقد رأيتها في أجزاء قليلة وواجهتها بلا شك في تحليلك الخاص، وقد حان الوقت الآن لتقديم مجموعة من التقنيات المفيدة للتعامل معها.
تكون السنة كبيسة إذا كانت تقبل القسمة على 4، إلا إذا كانت تقبل القسمة أيضاً على 100، ما لم تكن تقبل القسمة على 400 أيضاً. بمعنى آخر، في كل مجموعة من 400 سنة، هناك 97 سنة كبيسة.↩︎
قد تتساءل عما يرمز إليه الاختصار UTC. إنه تسوية بين الاسم الإنجليزي “Coordinated Universal Time” والفرنسي “Temps Universel Coordonné”.↩︎
لا توجد جوائز لمن يحزر الدولة التي ابتكرت نظام خطوط الطول.↩︎
