في أجزاء أخرى من المنهج، رأينا أنواعًا مختلفة من البيانات ذات خصائص متباينة.
سيتناول هذا المفهوم طرق تخزين بيانات متعددة الأبعاد وغير متجانسة. عمليًا، تكون معظم البيانات الواقعية على هذا النحو، لذا نصل الآن إلى جوهر الطريقة التي تُستخدم بها R في الممارسة العملية (في معظم الأحيان).
على مدى العقود، أضافت R أنواع بيانات متعددة للتعامل مع البيانات الجدولية.
سيركز هذا المنهج أساسًا على tibbles، لكن من المفيد معرفة بعض البدائل.
data.table
تُعد كل من tibbles (الموضحة أدناه) و data.tables محاولتين محترمتين لتحسين data.frame التقليدية في Base R.
لا مفر من كثرة الجدل حول أيهما «أفضل».
ربما يوجد قدر من التوافق حول النقاط التالية (حتى إن كان سيُنتقد بأنه تبسيطي).
tibble مُحسّنة أساسًا لسهولة الاستخدام والتكامل مع منظومة Tidyverse.data.table مُحسّنة أساسًا للقوة الصِرفة وقابلية التوسع، لا سيما عند العمل مع مجموعات بيانات بالغة الضخامة.على أي حال، لا تتوفر data.table داخل Exercism، لذا نذكرها هنا لاكتمال الصورة فقط.
data.frame
في Base R، data.frame هي list من vectors متساوية الطول.
يمكن تصورها كجدول بيانات مستطيل الشكل، يكون فيه كل عمود متجانسًا، في حين يمكن لكل صف أن يحتوي (وغالبًا ما يحتوي) على أنواع مختلفة من البيانات.
مثال يوضح ذلك:
# create the column vectors
languages <- c("Fortran", "R", "Python", "Julia")
created <- c(1957, 1993, 1991, 2012)
has.syllabus <- c(FALSE, TRUE, TRUE, TRUE)
# join columns to create the dataframe
df <- data.frame(languages, created, has.syllabus)
df
#> languages created has.syllabus
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
# look at the structure
str(df)
#> 'data.frame': 4 obs. of 3 variables:
#> $ languages : chr "Fortran" "R" "Python" "Julia"
#> $ created : num 1957 1993 1991 2012
#> $ has.syllabus: logi FALSE TRUE TRUE TRUE
لدينا عمود من السلاسل النصية، وعمود من الأعداد، وعمود من القيم المنطقية. عند التوسع، تكون هذه طريقة بديهية لتمثيل الكثير من مجموعات البيانات الواقعية.
tibble
تصميم data.frame قديم.
أدت خبرة تمتد لعقود، إضافة إلى تغيّر أنماط استخدام R، إلى إعادة تصميم أنتجت بديلًا محدّثًا في Tidyverse: tibbles.
مقارنةً بـ Base R، تتميّز tibbles بما يلي:
باختصار، تهدف tibble إلى «أن تفعل أقل وتشكو أكثر»، ويوصف ذلك أيضًا بأنه «كسول ومتذمر».
النوعان قابلان للتبادل عادةً: أي دالة تقبل data.frame ستقبل أيضًا tibble، والعكس صحيح.
التحويل بين النوعين سهل، عبر as_tibble(df) و as.data.frame(tbl).
في الأعمال الجديدة، سيساعدك استخدام tibbles على الأرجح في كتابة كود أكثر متانة.
غير أن الكود القديم والبيانات القديمة وفيرة جدًا في عالم R، لذا يُرجّح أن تظل data.frame شائعة لفترة طويلة.
# column vectors are same as for data.frame
library(tibble)
tbl <- tibble(languages, created, has.syllabus)
tbl
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
str(tbl)
#> tibble [4 × 3] (S3: tbl_df/tbl/data.frame)
#> $ languages : chr [1:4] "Fortran" "R" "Python" "Julia"
#> $ created : num [1:4] 1957 1993 1991 2012
#> $ has.syllabus: logi [1:4] FALSE TRUE TRUE TRUE
لاحظ صيغة الطباعة الافتراضية: يُطبع سطر التعليق الذي يحمل الأبعاد تلقائيًا، وتُعرض أنواع الأعمدة أيضًا.
تُعد tibbles جزءًا أساسيًا من Tidyverse، لذا أضفها باستخدام library(tibble) أو library(tidyverse).
الوثائق موسّعة إلى حد كبير، على نمط Tidyverse:
بأبسط صورة، يمكننا استخدام الدالة tibble() لضم متجهات الأعمدة، تمامًا كما في data.frame().
عُرض مثال على ذلك في قسم سابق.
إذا كان إدخال القيم حسب الصفوف أكثر ملاءمة، فالدالة المقابلة هي tribble().
tbl_r <- tribble(
# column names marked with tilde prefix
~languages, ~created, ~has.syllabus,
"Fortran", 1957, FALSE,
"R", 1993, TRUE,
"Python", 1991, TRUE,
"Julia", 2012, TRUE
)
tbl_r
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
عمليًا، توجد عشرات الطرق لإنشاء tibbles، إذ إنها صيغة الإخراج الافتراضية لمجموعة متنوعة من دوال Tidyverse. سنعاود الحديث عن هذا في مفهوم لاحق.
تناول مفهوم البرمجة الوظيفية مكتبة purrr للتعامل مع المتجهات والقوائم (بنى بيانات أحادية البعد).
أما بالنسبة لإطارات البيانات (سواء التقليدية منها أو tibbles)، فالمكتبة المقابلة هي dplyr.
قدّمنا dplyr سابقًا في مفهوم Switch.
اكتفينا هناك ببضع دوال مساعدة، لكن يمكننا الآن البدء في استكشاف بقية هذه المكتبة الضخمة.
يمكن التعامل مع إطارات البيانات، بما فيها tibbles، كقوائم من متجهات الأعمدة، لذا تسترجع فهرسة القوائم عمودًا محددًا.
tbl
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
tbl$created
#> [1] 1957 1993 1991 2012
يمكن أيضًا فهرسة إطار البيانات بفهرسة على نمط المصفوفات.
tbl[c(2, 4), 1:2]
# A tibble: 2 × 2
#> languages created
#> <chr> <dbl>
#> 1 R 1993
#> 2 Julia 2012
في R الحديثة مع منظومة Tidyverse، تكون دوال dplyr عمومًا أكثر مرونة وملاءمة، وستكون محور التركيز في بقية هذا المفهوم.
احصل على عمود واحد باستخدام pull() بالاسم أو بالرقم التسلسلي (الأرقام السالبة للعد من اليمين إلى اليسار).
tbl |> pull(created)
#> [1] 1957 1993 1991 2012
هذه هي النتيجة نفسها التي يعطيها tbl$created، لكن باستخدام دالة مناسبة لخط الأنابيب.
للحصول على عدة أعمدة، الدالة المناسبة هي select()، وهي عالية التنوع.
احصل على أعمدة (أو احذفها) بناءً على خصائص اسمها أو نوعها.
# Range with position and/or name
tbl |> select(1:created)
# A tibble: 4 × 2
#> languages created
#> <chr> <dbl>
#> 1 Fortran 1957
#> 2 R 1993
#> 3 Python 1991
#> 4 Julia 2012
# Exclude a column
tbl |> select(!created)
# A tibble: 4 × 2
#> languages has.syllabus
#> <chr> <lgl>
#> 1 Fortran FALSE
#> 2 R TRUE
#> 3 Python TRUE
#> 4 Julia TRUE
# Use type of column
tbl |> select(where(is.numeric))
# A tibble: 4 × 1
#> created
#> <dbl>
#> 1 1957
#> 2 1993
#> 3 1991
#> 4 2012
يُسمح بمعايير متعددة، باستخدام العوامل المنطقية & و | و ! (و، أو، ليس).
أسماء الأعمدة التي تكون معرّفات R صالحة لا تحتاج إلى علامات اقتباس داخل select().
يمكن إحاطة الأسماء غير الصالحة (مثل التي تتضمن مسافات) بعلامات الاقتباس الخلفية، وإن كانت إعادة تسميتها قد تكون أفضل.
يمكن للدالة select() العمل مع مجموعة من الدوال المساعدة لاختيار أسماء الأعمدة: starts_with و contains و num_range وغيرها الكثير.
matches تتيح مطابقة كاملة باستخدام التعبيرات النمطية.
راجع الوثائق للحصول على التفاصيل.
تبدو هذه القدرات عبثية بعض الشيء مع إطار البيانات البسيط الذي لدينا عن اللغات.
لحسن الحظ، يتضمن dplyr إطار tibble المسمى starwars، ما يمنحنا شيئًا أكبر للتدريب عليه.
# limit display to top 3 rows of non-list columns
starwars |>
select(!where(is.list)) |>
head(3)
# A tibble: 3 × 11
#> name height mass hair_color skin_color eye_color birth_year sex gender homeworld species
#> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <chr>
#> 1 Luke Skywalker 172 77 blond fair blue 19 male masculine Tatooine Human
#> 2 C-3PO 167 75 NA gold yellow 112 none masculine Tatooine Droid
#> 3 R2-D2 96 32 NA white, blue red 33 none masculine Naboo Droid
# pick a subset of columns
starwars |>
select(name | ends_with("color")) |>
head(5)
# A tibble: 5 × 4
#> name hair_color skin_color eye_color
#> <chr> <chr> <chr> <chr>
#> 1 Luke Skywalker blond fair blue
#> 2 C-3PO NA gold yellow
#> 3 R2-D2 NA white, blue red
#> 4 Darth Vader none white yellow
#> 5 Leia Organa brown light brown
من الواضح أن dplyr توفر طرقًا قوية لاختيار الأعمدة بالاسم.
هل يمكننا فعل أشياء مشابهة بأسماء الصفوف؟
لا!
يمكن أن تحتوي إطارات بيانات R التقليدية على أسماء صفوف، لكن (بعد تاريخ من الأخطاء ومشكلات الأداء) أسماء الصفوف غير مسموح بها في tibbles.
إذا أردت أسماء، فضعها في عمود من النوع <chr> (عادةً العمود 1)، يُستخدم مثل أي عمود آخر.
ستُنشئ دوال الاستيراد مثل as_tibble() هذا العمود تلقائيًا عند استيراد بيانات ذات صفوف مسماة.
إذا بدا هذا القيد على أسماء الصفوف مقيّدًا على نحو غريب، فتذكّر أن معظم أنظمة قواعد البيانات الكبيرة تتعامل مع الجداول بالطريقة نفسها: Oracle و SQL Server و PostgreSQL و MySQL...
احصل على الصفوف المطابقة لمعايير معينة باستخدام filter()، أو استبعدها باستخدام filter_out().
starwars |>
select(name:mass) |>
filter(between(height, 150, 165) & !is.na(mass))
# A tibble: 4 × 3
#> name height mass
#> <chr> <int> <dbl>
#> 1 Leia Organa 150 49
#> 2 Beru Whitesun Lars 165 75
#> 3 Nien Nunb 160 68
#> 4 Ben Quadinaros 163 65
يمكن أن تكون معايير التصفية بالغة التعقيد، لكنها تستند دائمًا إلى محتويات الصفوف.
إذا كانت أرقام الصفوف معروفة، يمكننا استخدام مجموعة متنوعة من دوال slice().
starwars |>
select(name | homeworld) |>
slice(20:25)
# A tibble: 6 × 2
#> name homeworld
#> <chr> <chr>
#> 1 Palpatine Naboo
#> 2 Boba Fett Kamino
#> 3 IG-88 NA
#> 4 Bossk Trandosha
#> 5 Lando Calrissian Socorro
#> 6 Lobot Bespin
# random sample of rows
starwars |>
select(name | homeworld) |>
slice_sample(n = 4)
# A tibble: 4 × 2
#> name homeworld
#> <chr> <chr>
#> 1 Shaak Ti Shili
#> 2 Luminara Unduli Mirial
#> 3 Grievous Kalee
#> 4 Palpatine Naboo
لإزالة الصفوف المكررة، استخدم distinct().
تنبيه أول: السلوك الافتراضي النسخ عند التعديل يعني أن إطار tibble الأصلي يظل عادةً دون تغيير.
تُطبَّق معظم التعديلات حسب الأعمدة.
يمكن تغيير أسماء الأعمدة باستخدام rename(newname = oldname)، أو rename_with() لتطبيق دالة.
من الاستخدامات النموذجية تنظيف الأسماء المستوردة لتسهيل التعامل معها في R، عبر إزالة المسافات البيضاء وفرض تنسيق موحد للأسماء المترابطة.
لاحظ الصياغة داخل rename().
محتوى العمود oldname مرتبط بالاسم newname، ومن هنا يأتي الترتيب.
يمكن تغيير ترتيب الأعمدة باستخدام relocate().
يُنقل العمود أو الأعمدة المحددة إلى الموضع أو المواضع الأقصى يسارًا افتراضيًا، لكن يمكن استخدام الوسيط .before أو .after لضبط أدق للموضع.
sw <- starwars |> select(name:species) |> slice(1:4)
sw
# A tibble: 4 × 11
#> name height mass hair_color skin_color eye_color birth_year sex gender homeworld species
#> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <chr>
#> 1 Luke Skywalker 172 77 blond fair blue 19 male masculine Tatooine Human
#> 2 C-3PO 167 75 NA gold yellow 112 none masculine Tatooine Droid
#> 3 R2-D2 96 32 NA white, blue red 33 none masculine Naboo Droid
#> 4 Darth Vader 202 136 none white yellow 41.9 male masculine Tatooine Human
sw |> relocate(c(species, homeworld), .after = name)
# A tibble: 4 × 11
#> name species homeworld height mass hair_color skin_color eye_color birth_year sex gender
#> <chr> <chr> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
#> 1 Luke Skywalker Human Tatooine 172 77 blond fair blue 19 male masculine
#> 2 C-3PO Droid Tatooine 167 75 NA gold yellow 112 none masculine
#> 3 R2-D2 Droid Naboo 96 32 NA white, blue red 33 none masculine
#> 4 Darth Vader Human Tatooine 202 136 none white yellow 41.9 male masculine
لإجراء تغييرات أكبر، تتيح لك mutate() ما يلي:
NULL
من الواضح أن mutate() قوية، وقد تكون مربكة، وسبب لامتنان كبير للنسخ عند التعديل.
لا يوجد سبب واضح للاهتمام بـ مؤشر كتلة الجسم لشخصيات Star Wars، لكن على سبيل الاحتياط فقط:
starwars |>
select(c(name, species, height, mass)) |>
mutate(BMI = mass / (height / 100)^2) |>
head(4)
# A tibble: 4 × 5
#> name species height mass BMI
#> <chr> <chr> <int> <dbl> <dbl>
#> 1 Luke Skywalker Human 172 77 26.0
#> 2 C-3PO Droid 167 75 26.9
#> 3 R2-D2 Droid 96 32 34.7
#> 4 Darth Vader Human 202 136 33.3
عندما تريد العمل على مجموعة فرعية من الأعمدة باستخدام دوال مثل mutate()، فإن تسلسل select() |> mutate() في المثال أعلاه أحد الخيارات.
في النتيجة لن تظهر إلا الأعمدة المحددة.
وبدلًا من ذلك، قد يكون من الملائم استخدام pick() داخل استدعاء mutate():
starwars |> mutate(pick(c(name, species, height, mass)), BMI = mass / (height / 100)^2) |> head(4)
When you want to operate on a subset of the columns with functions such as `mutate()`, the `select() |> mutate()` sequence in the above example is one option.
Only the selected columns will be in the result.
Alternatively, it can be convenient to use `pick()` _within_ the `mutate()` call:
```R
starwars |> mutate(pick(c(name, species, height, mass)), BMI = mass / (height / 100)^2) |> head(4)
# A tibble: 4 × 15
#> name height mass hair_color skin_color eye_color birth_year sex gender homeworld species films vehicles
#> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr> <chr> <chr> <lis> <list>
#> 1 Luke Skywal… 172 77 blond fair blue 19 male mascu… Tatooine Human <chr> <chr>
#> 2 C-3PO 167 75 NA gold yellow 112 none mascu… Tatooine Droid <chr> <chr>
#> 3 R2-D2 96 32 NA white, bl… red 33 none mascu… Naboo Droid <chr> <chr>
#> 4 Darth Vader 202 136 none white yellow 41.9 male mascu… Tatooine Human <chr> <chr>
# ℹ 2 more variables: starships <list>, BMI <dbl>
لا تُستخدم في التعديل إلا الأعمدة المختارة بواسطة pick، لكن تُعاد جميع الأعمدة.
العمليات حسب الصفوف أقل شيوعًا عند تعديل إطار tibble واحد (سيُناقش دمج عدة tibbles في مفهوم لاحق).
وثمة استثناء واحد: arrange() ترتب الصفوف حسب القيم في عمود واحد أو أكثر.
tbl
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 R 1993 TRUE
#> 3 Python 1991 TRUE
#> 4 Julia 2012 TRUE
tbl |> arrange(languages)
# A tibble: 4 × 3
#> languages created has.syllabus
#> <chr> <dbl> <lgl>
#> 1 Fortran 1957 FALSE
#> 2 Julia 2012 TRUE
#> 3 Python 1991 TRUE
#> 4 R 1993 TRUE
إطارات البيانات، سواء التقليدية منها أو tibbles، محورية في الطريقة التي تُستخدم بها R الحديثة عادةً.
تأخذ معظم دوال Tidyverse (وليس dplyr فقط) tibbles كمدخلات و/أو تنشئها كمخرجات.
قدّم هذا المفهوم مجرد مقدمة موجزة، بالكاد تخدش سطح ما هو ممكن.
ستناقش مفاهيم لاحقة عدة جوانب أخرى من إطارات البيانات (ضمن القيود التقنية لـ Exercism).