ট্র্যাক
/
R
R
/
অনুশীলনী
/
গাছ ও টিবল-েশন
গাছ ও টিবল-েশন

গাছ ও টিবল-েশন

লার্নিং অনুশীলনী

ভূমিকা

সিলেবাসের অন্য অংশগুলোতে আমরা ভিন্ন ভিন্ন বৈশিষ্ট্যসম্পন্ন নানা ডেটা টাইপ দেখেছি।

  • অ্যাটমিক ভেক্টর একমাত্রিক এবং টাইপে সমজাতীয়।
  • লিস্ট একমাত্রিক, আর এর এলিমেন্টগুলো ভিন্ন ভিন্ন টাইপের হতে পারে।
  • ম্যাট্রিক্স ও অ্যারে বহুমাত্রিক এবং সমজাতীয়।

এই কনসেপ্টে আমরা বহুমাত্রিক, ভিন্নধর্মী ডেটা সংরক্ষণের উপায়গুলো দেখব। বাস্তবে বেশিরভাগ রিয়েল-ওয়ার্ল্ড ডেটাই এ রকম, তাই আমরা এখন R আসলে (বেশিরভাগ ক্ষেত্রে) কীভাবে ব্যবহৃত হয় তার একেবারে কেন্দ্রে পৌঁছে যাচ্ছি।

ডেটাফ্রেমের প্রকারভেদ

কয়েক দশক ধরে R ট্যাবুলার ডেটা সামলানোর জন্য একাধিক ডেটা টাইপ যোগ করেছে।

এই সিলেবাসে মূলত টিবল নিয়েই আলোচনা হবে, তবে বিকল্পগুলো সম্পর্কেও জানা কাজে লাগে।

data.frame

বেস R-এ একটি data.frame হলো সমান দৈর্ঘ্যের vectors-গুলোর একটি list। এটাকে ডেটার একটি আয়তাকার টেবিল হিসেবে ভাবা যায়, যেখানে প্রতিটি কলাম সমজাতীয়, কিন্তু প্রতিটি সারিতে ভিন্ন ভিন্ন টাইপের ডেটা থাকতে পারে (এবং সাধারণত থাকে)।

এটি বোঝানোর জন্য একটি উদাহরণ:

# 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-এ এসেছে আধুনিক বিকল্প: টিবল।

বেস R-এর তুলনায় টিবলে আছে:

  • ভিন্ন ডিফল্ট, যাতে কমন সমস্যা কমানো যায়।
  • ইনপুটের সময় ডেটা টাইপ জোর করে বদলানোর প্রবণতা কম।
  • আরও বেশি ও আরও স্পষ্ট এরর মেসেজ।
  • ভিন্ন, সাধারণত আরও ভালো, ডিসপ্লে ফরম্যাট।

সংক্ষেপে, একটি tibble-এর লক্ষ্য হলো "কম কাজ করা আর বেশি অভিযোগ করা", যাকে "অলস ও রুক্ষ"ও বলা হয়।

টাইপগুলো সাধারণত বিনিময়যোগ্য: যে ফাংশন data.frame গ্রহণ করে, সেটি tibble-ও গ্রহণ করবে, এবং উল্টোটাও।

টাইপগুলোর মধ্যে রূপান্তর সহজ, as_tibble(df) ও as.data.frame(tbl) দিয়ে।

নতুন কাজের জন্য, টিবল ব্যবহার করলে সম্ভবত আরও শক্তসমর্থ কোড লিখতে সাহায্য হবে। তবে 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      

ডিফল্ট প্রিন্ট ফরম্যাটটি লক্ষ করুন: মাত্রা লেখা কমেন্ট লাইনটি স্বয়ংক্রিয়ভাবে প্রিন্ট হয়, আর কলামের টাইপগুলোও দেখানো হয়।

টিবল নিয়ে কাজ করা

টিবল হলো Tidyverse-এর একটি মূল অংশ, তাই library(tibble) বা library(tidyverse) যেকোনোটি দিয়ে এটি যোগ করুন।

একটি টিবল তৈরি করা

সবচেয়ে সহজে, data.frame()-এর মতোই, কলাম ভেক্টরগুলো জোড়া লাগাতে আমরা tibble() ফাংশনটি ব্যবহার করতে পারি। এর একটি উদাহরণ আগের একটি অংশে দেখানো হয়েছে।

সারি ধরে মান লিখতে বেশি সুবিধা হলে, তার জন্য সংশ্লিষ্ট ফাংশনটি হলো tribble()।

বাস্তবে টিবল তৈরির ডজনখানেক উপায় আছে, কারণ Tidyverse-এর নানা রকম ফাংশনের ডিফল্ট আউটপুট ফরম্যাটই হলো টিবল।

একটি টিবল ম্যানিপুলেট করা

ফাংশনাল প্রোগ্রামিং কনসেপ্টে ভেক্টর ও লিস্ট (একমাত্রিক ডেটা স্ট্রাকচার) ম্যানিপুলেট করার জন্য purrr লাইব্রেরি নিয়ে আলোচনা করা হয়েছে।

ডেটাফ্রেমের জন্য (সেটা প্রচলিত হোক বা টিবল), ব্যবহার করার মতো সংশ্লিষ্ট লাইব্রেরি হলো dplyr।

সাবসেট করা

ডেটাফ্রেম, টিবলসহ, কলাম ভেক্টরের লিস্ট হিসেবে ধরা যায়, তাই লিস্ট ইনডেক্সিং দিয়ে নির্দিষ্ট একটি কলাম পাওয়া যায়।

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

Tidyverse ইকোসিস্টেমসহ আধুনিক R-এ, 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

বুলিয়ান অপারেটর &, | ও ! (and, or, not) ব্যবহার করে একাধিক শর্ত দেওয়া যায়।

ভাষার এই খেলনা ডেটাফ্রেমটি নিয়ে এত ক্ষমতা বেশ হাস্যকরই লাগে। ভাগ্যক্রমে, dplyr-এর সঙ্গে starwars টিবলটি দেওয়া আছে, যা দিয়ে অনুশীলন করার জন্য বড় কিছু পাওয়া যায়।

# 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   

একটি টিবল পরিবর্তন করা

প্রথম সতর্কতা: কপি-অন-মডিফাই ডিফল্টের কারণে মূল টিবলটি সাধারণত অপরিবর্তিত থাকে।

বেশিরভাগ পরিবর্তন কলাম-ভিত্তিকভাবে প্রয়োগ করা হয়।

কলামের নাম 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

সারি-ভিত্তিক অপারেশন একক টিবল পরিবর্তনের জন্য ততটা কমন নয় (একাধিক টিবল মার্জ করা নিয়ে পরের কোনো কনসেপ্টে আলোচনা হবে)।

একটি ব্যতিক্রম: 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।

trees ডেটাসেটের Girth কলামের নাম সংশোধন করে Diameter করে দেয় এমন একটি data.frame/tibble ডিফাইন করুন, যার নাম tree_data।

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) ফাংশনটি ডিফাইন করুন, যা একটি ডেটাফ্রেম ও একটি সংখ্যাসূচক আর্গুমেন্ট নেয়। ফাংশনটি দুটি অতিরিক্ত কলামসহ একটি নতুন ডেটাফ্রেম রিটার্ন করবে: 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) ফাংশনটি ডিফাইন করুন, যা ইনপুট হিসেবে একটি ডেটাফ্রেম নেয়। এটি 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) ফাংশনটি ডিফাইন করুন। সীমার মধ্যে থাকা সারিগুলোসহ 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

গাছ ও টিবল-েশন শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, R ট্র্যাকের 21টি কনসেপ্ট111টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।