مسیرها
/
R
R
/
تمرین‌ها
/
درختان و دردسرهای Tibble
درختان و دردسرهای Tibble

درختان و دردسرهای Tibble

تمرین یادگیری

مقدمه

در بخش‌های دیگر برنامه‌ی درسی، انواع داده‌ای گوناگون با ویژگی‌های متفاوت را دیده‌ایم.

  • بردارهای اتمی یک‌بعدی و از نظر نوع همگن‌اند.
  • لیست‌ها یک‌بعدی‌اند و عناصرشان می‌توانند از انواع ناهمگن باشند.
  • ماتریس‌ها و آرایه‌ها چندبعدی و همگن‌اند.

این مفهوم به روش‌های ذخیره‌ی داده‌های چندبعدی و ناهمگن می‌پردازد. در عمل، بیشترِ داده‌های دنیای واقعی این‌گونه‌اند، پس اکنون به قلب آن چیزی می‌رسیم که R (بیشتر) در عمل برای آن به کار می‌رود.

گونه‌های دیتافریم

در طول دهه‌ها، R انواع داده‌ای متعددی برای کار با داده‌های جدولی افزوده است.

این برنامه‌ی درسی عمدتاً روی tibbleها تمرکز می‌کند، اما آگاهی از جایگزین‌ها هم مفید است.

data.frame

در 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

یک ستون از رشته‌های کاراکتری، یک ستون از اعداد و یک ستون از مقادیر منطقی داریم. در مقیاس بزرگ‌تر، این روشی شهودی برای نمایش بسیاری از مجموعه‌های داده‌ی دنیای واقعی است.

tibble

طراحی data.frame قدیمی است.

تجربه‌ی چند دهه، به‌علاوه‌ی تغییر الگوهای استفاده از R، به بازطراحی انجامید و جایگزینی مدرن در Tidyverse پدید آورد: tibbleها.

در مقایسه با R پایه، tibbleها ویژگی‌های زیر را دارند:

  • پیش‌فرض‌های متفاوت، برای کاهش مشکلات رایج.
  • تمایل کمتر به تبدیل اجباری نوع داده‌ها در هنگام ورود.
  • پیام‌های خطای بیشتر و شفاف‌تر.
  • قالب‌های نمایش متفاوت، که معمولاً بهترند.

به‌طور خلاصه، یک tibble می‌کوشد «کمتر کار کند و بیشتر گلایه کند»، که آن را «تنبل و بدعنق» هم توصیف کرده‌اند.

این انواع معمولاً جای هم قابل استفاده‌اند: هر تابعی که یک data.frame را بپذیرد، یک tibble را هم می‌پذیرد و برعکس.

تبدیل میان این انواع آسان است، با as_tibble(df) و as.data.frame(tbl).

برای کارهای جدید، استفاده از tibbleها احتمالاً به شما کمک می‌کند کدی پایدارتر بنویسید. اما کد و داده‌ی قدیمی در دنیای 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      

به قالب پیش‌فرض چاپ دقت کنید: سطر کامنت دارای ابعاد به‌طور خودکار چاپ می‌شود و نوع ستون‌ها نیز نمایش داده می‌شود.

کار با tibbleها

tibbleها بخشی اصلی از Tidyverse هستند، پس آن‌ها را با library(tibble) یا library(tidyverse) اضافه کنید.

ساختن یک tibble

به ساده‌ترین شکل، می‌توانیم از تابع tibble() برای پیوستن بردارهای ستونی استفاده کنیم، همان‌طور که برای data.frame() گفتیم. نمونه‌ای از آن در بخش پیشین نشان داده شد.

اگر وارد کردن مقادیر به‌صورت سطری راحت‌تر باشد، تابع متناظر tribble() است.

در عمل، ده‌ها روش برای ساختن tibble وجود دارد، چون آن‌ها قالب خروجی پیش‌فرض طیف متنوعی از توابع Tidyverse هستند.

دستکاری یک tibble

مفهوم برنامه‌نویسی تابعی درباره‌ی کتابخانه‌ی purrr برای دستکاری بردارها و لیست‌ها (ساختارهای داده‌ی یک‌بعدی) بحث کرد.

برای دیتافریم‌ها (چه سنتی و چه tibble)، کتابخانه‌ی متناظر برای استفاده dplyr است.

زیرمجموعه‌گیری

دیتافریم‌ها، از جمله tibbleها، را می‌توان به‌عنوان لیستی از بردارهای ستونی در نظر گرفت، پس اندیس‌گذاری لیست یک ستون مشخص را بازیابی می‌کند.

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      

# Use type of column
tbl |> select(where(is.numeric))
  # A tibble: 4 × 1
#>   created
#>     <dbl>
#> 1    1957
#> 2    1993
#> 3    1991
#> 4    2012

معیارهای متعدد مجازند، با استفاده از عملگرهای منطقی &، | و ! (و، یا، نه).

چنین قدرتی با دیتافریم اسباب‌بازی ما از زبان‌ها کمی بی‌معنی به نظر می‌رسد. خوشبختانه، tibble با اسم starwars در dplyr گنجانده شده است و مجموعه‌ی بزرگ‌تری برای تمرین در اختیار ما می‌گذارد.

# 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    

عملیات سطر‌به‌سطر

سطرهای منطبق با برخی معیارها را با 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   

تغییر یک tibble

نخست یک هشدار: پیش‌فرض «کپی هنگام تغییر» به این معناست که tibble اصلی معمولاً بدون تغییر می‌ماند.

بیشتر تغییرها ستون‌به‌ستون اعمال می‌شوند.

اسم ستون‌ها را می‌توان با rename(newname = oldname) تغییر داد، یا با rename_with() برای اعمال یک تابع. به نحوه‌ی نگارش درون 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() قدرتمند و به‌طور بالقوه گیج‌کننده است و دلیلی است برای سپاسگزاری فراوان از کپی هنگام تغییر.

دلیل آشکاری برای اهمیت دادن به شاخص توده‌ی بدنی شخصیت‌های جنگ ستارگان وجود ندارد، اما محض احتیاط:

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

عملیات سطر‌به‌سطر برای تغییر یک tibble واحد کمتر رایج است (ادغام چند tibble در مفهومی بعدی بحث خواهد شد).

یک استثنا: 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     

دستورالعمل‌ها

یک باغ گیلاس بزرگ معمولاً موجودی کوچکی از درختان مازاد دارد که می‌خواهد آن‌ها را به عنوان چوب عرضه کند. این کار به سالم ماندن باغ کمک می‌کند و در همان حال درآمد اضافی هم به همراه دارد. آن‌ها از شما خواسته‌اند کدی بنویسید که در فرایند انتخاب درخت، وقتی مشتری بالقوه‌ای تماس می‌گیرد، کمک کند.

Note

کتابخانه‌ی داخلی datasets مجموعه‌داده‌های نمونه‌ی زیادی دارد و این تمرین از مجموعه‌دادهی trees استفاده می‌کند. برای فراخوانی مجموعه‌داده، کافی است از اسم trees استفاده کنید.

library(datasets) # loading is optional

trees |> head(3)
#>    A data.frame: 3 × 3 	
#>   Girth	Height	Volume
#>	 <dbl>	 <dbl>	 <dbl>
#> 1   8.3	    70	  10.3  
#> 2   8.6	    65	  10.3
#> 3   8.8	    63	  10.2

به سه ستون توجه کنید: Girth، Height و Volume

مجموعه‌داده در ابتدا به شکل یک data.frame است، اما ممکن است بخواهید آن را به صورت یک tibble به کار ببرید.

1. تغییر نام یک ستون

متأسفانه یک خطای اداری رخ داده که هرگز اصلاح نشده است و اسم یکی از ستون‌های مجموعه‌داده Girth است، در حالی که این ستون در واقع Diameter است.

یک data.frame یا tibble به اسم tree_data تعریف کنید که اسم ستون Girth را در trees به Diameter اصلاح می‌کند.

head(tree_data, 3)
#>     A tibble: 3 × 3 
#> Diameter	 Height	Volume
#>    <dbl>	  <dbl>	 <dbl>
#>      8.3	     70	  10.3
#>      8.6	     65	  10.3
#>      8.8	     63	  10.2

2. افزودن ستون‌های محیط و وزن

محیط (که به آن دور هم می‌گویند) هنوز اندازه‌گیری مفیدی است و وزن چوب هم همین‌طور. محیط به‌سادگی با πD محاسبه می‌شود، که در آن D همان قطر است. وزن همه‌ی چوب را می‌توان با ρV محاسبه کرد، که در آن ρ = 35 چگالی چوب گیلاس و V حجم چوب است.

تابع girth_n_weight(data, rnd_digits) را تعریف کنید که یک data.frame و آرگومان‌های عددی می‌گیرد. این تابع باید یک data.frame جدید با دو ستون اضافه برگرداند: Girth و Weight که تا تعداد رقم مناسب مشخص‌شده در rnd_digits گرد شده‌اند.

girth_n_weight(tree_data, 1) |> head(3)
#>           A tibble: 3 × 5 
#> Diameter  Height	 Volume	 Girth	Weight
#>    <dbl>	  <dbl>	  <dbl>	 <dbl>	 <dbl>
#>      8.3	     70	   10.3	  26.1	 360.5
#>      8.6	     65	   10.3	  27.0	 360.5
#>      8.8	     63	   10.2	  27.6	 357.0

3. نسخه‌ی باغ از مجموعه‌داده

باغ برای هر مشتری بالقوه یک نسخه‌ی ویژه از مجموعه‌داده نگه می‌دارد تا به فروش کمک کند. در این مجموعه‌داده، مرتبط‌ترین ستون‌ها، یعنی Weight و Height، به ابتدا منتقل شده‌اند و ردیف‌ها بر اساس Weight مرتب شده‌اند.

تابع orchard_copy(data, important_cols) را تعریف کنید که یک data.frame به عنوان ورودی می‌گیرد. این تابع باید یک data.frame جدید برگرداند که در آن Weight و Height به ابتدا منتقل شده‌اند و ردیف‌ها بر اساس Weight مرتب شده‌اند.

tree_data |> girth_n_weight() |> orchard_copy() |> head(3)
#>            A tibble: 3 × 5 
#> Weight	Height	Diameter	Volume	Girth
#>  <dbl>	 <dbl>	   <dbl>	 <dbl>	<dbl>
#>    357	    63	       9	    10	   28
#>    360	    70	       8	    10	   26
#>    360	    65	       9	    10	   27

4. نسخه‌ی مشتری از مجموعه‌داده

هر مشتری بالقوه بر اساس ترجیحات خود یک نسخه‌ی شخصی از مجموعه‌داده دریافت می‌کند. برای این کار، مشتری می‌تواند این‌ها را مشخص کند: کمینه‌ی ارتفاع، بیشینه‌ی ارتفاع و بیشینه‌ی وزن هر درخت.

تابع customer_copy(data, min_height, max_height, max_weight) را تعریف کنید. یک نسخه‌ی مشتری از data.frame را با ستون‌های Height، Weight، Diameter و Girth برگردانید، با ردیف‌هایی که در محدوده‌ی قیدها قرار دارند.

tree_data |> 
    girth_n_weight(1) |> 
    orchard_copy() |>
    customer_copy(65, 75, 1500) |> 
    head(3)
#>        A tibble: 3 × 4 
#> Height	Weight	Diameter	Girth
#>  <dbl>	 <dbl>	   <dbl>	<dbl>
#>     70	 360.5	     8.3	 26.1
#>     65	 360.5	     8.6	 27.0
#>     66	 546.0	    11.0	 34.6
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
R Exercism

آماده‌اید درختان و دردسرهای Tibble را شروع کنید؟

در Exercism ثبت‌نام کنید تا R را همراه با 21 مفهوم111 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.