المسارات
/
R
R
/
المنهج
/
إطارات البيانات
إط

إطارات البيانات في R

1 تمرين

نبذة عن إطارات البيانات

في أجزاء أخرى من المنهج، رأينا أنواعًا مختلفة من البيانات ذات خصائص متباينة.

  • المتجهات الذرية أحادية البعد ومتجانسة في نوعها.
  • القوائم أحادية البعد ويمكن أن تكون عناصرها من أنواع غير متجانسة.
  • المصفوفات متعددة الأبعاد ومتجانسة.

سيتناول هذا المفهوم طرق تخزين بيانات متعددة الأبعاد وغير متجانسة. عمليًا، تكون معظم البيانات الواقعية على هذا النحو، لذا نصل الآن إلى جوهر الطريقة التي تُستخدم بها 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

تُعد tibbles جزءًا أساسيًا من Tidyverse، لذا أضفها باستخدام library(tibble) أو library(tidyverse).

الوثائق موسّعة إلى حد كبير، على نمط Tidyverse:

إنشاء tibble

بأبسط صورة، يمكننا استخدام الدالة 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. سنعاود الحديث عن هذا في مفهوم لاحق.

التعامل مع tibble

تناول مفهوم البرمجة الوظيفية مكتبة 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    

العمليات حسب الصفوف

Note

من الواضح أن 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

تنبيه أول: السلوك الافتراضي النسخ عند التعديل يعني أن إطار 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).

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم إطارات البيانات