সিলেবাসের অন্য জায়গায় আমরা ভিন্ন ভিন্ন বৈশিষ্ট্যের নানা ডেটা টাইপ দেখেছি।
এই কনসেপ্টে আমরা বহু-মাত্রিক ও ভিন্ন ধরনের ডেটা সংরক্ষণের উপায় দেখব। বাস্তবে দেখা যায়, বাস্তব-জগতের বেশিরভাগ ডেটাই এমন, তাই এখন আমরা R-কে (বেশিরভাগ ক্ষেত্রেই) কীভাবে ব্যবহার করা হয় সেই মূল জায়গায় পৌঁছে যাচ্ছি।
কয়েক দশক ধরে R টেবিল আকারের ডেটা সামলাতে একাধিক ডেটা টাইপ যোগ করেছে।
এই সিলেবাসে আমরা মূলত tibble-এর দিকেই মনোযোগ দেব, তবে কিছু বিকল্প সম্পর্কে জানাও কাজে আসবে।
data.tableTibble (নিচে বর্ণিত) আর data.table দুটিই Base R-এর প্রচলিত data.frame-কে উন্নত করার সমাদৃত প্রচেষ্টা।
কোনটা "ভালো", তা নিয়ে বিতর্ক অনিবার্য।
হয়তো নিচের বিষয়গুলো নিয়ে কিছুটা ঐকমত্য আছে (যদিও এগুলোকে অতিসরলীকরণ বলে সমালোচনা করা হবে)।
tibble মূলত ব্যবহারের সহজতা এবং Tidyverse ইকোসিস্টেমের সাথে সংযোগের জন্য অপটিমাইজ করা হয়েছে।data.table মূলত কাঁচা ক্ষমতা ও স্কেলযোগ্যতার জন্য অপটিমাইজ করা হয়েছে, বিশেষ করে খুব বড় ডেটাসেট নিয়ে কাজ করার সময়।যাই হোক, Exercism-এর ভিতরে data.table পাওয়া যায় না, তাই এখানে শুধু পূর্ণতার জন্য এর উল্লেখ করা হলো।
data.frameBase 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
# 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
এখানে আমাদের আছে ক্যারেক্টার স্ট্রিং-এর একটি কলাম, সংখ্যার একটি কলাম আর বুলিয়ান-এর একটি কলাম। আকারে বড় করলে দেখা যায়, বাস্তব জগতের অনেক ডেটাসমষ্টি উপস্থাপনের সহজবোধ্য উপায় হলো এটি।
tibbledata.frame-এর ডিজাইন পুরনো।
কয়েক দশকের অভিজ্ঞতা, সঙ্গে R কীভাবে ব্যবহৃত হয় তার বদলে যাওয়া ধরন, এসব মিলে একটি নতুন ডিজাইনের দিকে নিয়ে গেল, আর Tidyverse-এ তৈরি হলো একটি আধুনিক বিকল্প: tibble।
Base 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
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
ডিফল্ট প্রিন্ট ফরম্যাটটি খেয়াল করুন: মাত্রা লেখা কমেন্ট লাইনটি স্বয়ংক্রিয়ভাবে ছাপা হয়, আর কলামের টাইপও দেখানো হয়।
Tibble হলো Tidyverse-এর মূল অংশ, তাই library(tibble) বা library(tidyverse) দিয়ে এটি যোগ করুন।
ডকুমেন্টেশন বেশ বিস্তৃত, Tidyverse-এর ধরনে:
সবচেয়ে সহজে, data.frame()-এর মতোই, কলাম ভেক্টর জোড়া দিতে আমরা tibble() ফাংশনটি ব্যবহার করতে পারি।
এর একটি উদাহরণ আগের একটি অংশে দেখানো হয়েছে।
সারি ধরে মান লিখতে বেশি সুবিধা হলে, তার জন্য সংশ্লিষ্ট ফাংশনটি হলো 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
বাস্তবে tibble তৈরির ডজনখানেক উপায় আছে, কারণ Tidyverse-এর নানা রকম ফাংশনের ডিফল্ট আউটপুট ফরম্যাটই হলো tibble। ভবিষ্যতের একটি কনসেপ্টে আমরা এখানে ফিরে আসব।
ফাংশনাল প্রোগ্রামিং কনসেপ্টে ভেক্টর আর লিস্ট (১-মাত্রিক ডেটা স্ট্রাকচার) ম্যানিপুলেট করার purrr লাইব্রেরি নিয়ে আলোচনা করা হয়েছে।
ডেটাফ্রেমের জন্য (প্রচলিত হোক বা tibble), ব্যবহারযোগ্য সংশ্লিষ্ট লাইব্রেরিটি হলো dplyr।
আমরা আগে Switch কনসেপ্টে 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
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
# 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
একাধিক শর্ত দেওয়া যায়, বুলিয়ান অপারেটর &, | এবং ! (and, or, not) ব্যবহার করে।
বৈধ R আইডেন্টিফায়ার এমন কলামের নাম একটি select()-এর ভিতরে কোটেশন ছাড়াই চলে।
অবৈধ নাম (যেমন স্পেস থাকা নাম) ব্যাকটিক-এর ভিতরে রাখা যায়, যদিও সেগুলোর নাম বদলে ফেলা বরং ভালো হতে পারে।
select() ফাংশনটি কলামের নাম বাছাই করার অনেক হেল্পার ফাংশনের সাথে কাজ করতে পারে: starts_with, contains, num_range আর আরও নানা রকম।
matches পূর্ণ RegEx মিল খোঁজার সুযোগ দেয়।
বিস্তারিত জানতে ডকুমেন্টেশন দেখুন।
ভাষার ছোট্ট এই খেলনা ডেটাফ্রেম নিয়ে এত ক্ষমতা একেবারেই বাড়তি মনে হয়।
ভাগ্য ভালো, starwars tibble টি dplyr-এর সাথেই দেওয়া আছে, তাই অনুশীলনের জন্য বড় কিছু পাওয়া গেল।
# 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> কলামে (সাধারণত ১ নম্বর কলামে) রাখুন, যাকে অন্য যেকোনো কলামের মতোই ব্যবহার করা যায়।
নামযুক্ত সারি সহ ডেটা ইমপোর্ট করার সময় 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() ব্যবহার করুন।
প্রথম সতর্কতা: copy-on-modify ডিফল্টের কারণে মূল 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() শক্তিশালী, সম্ভাব্য বিভ্রান্তিকর, আর copy-on-modify-এর জন্য কৃতজ্ঞ থাকার একটি বড় কারণ।
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() ক্রমটি একটি উপায়।
ফলাফলে শুধু বাছাই করা কলামগুলোই থাকবে।
বিকল্পভাবে, mutate() কলের ভিতরে pick() ব্যবহার করাও সুবিধাজনক হতে পারে:
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 পরিবর্তনের ক্ষেত্রে সারিভিত্তিক অপারেশন কম দেখা যায় (একাধিক 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
ডেটাফ্রেম, প্রচলিত হোক বা tibble, আধুনিক R সাধারণত যেভাবে ব্যবহৃত হয় তার কেন্দ্রে রয়েছে।
Tidyverse-এর বেশিরভাগ ফাংশন (শুধু dplyr নয়) ইনপুট হিসেবে tibble নেয় এবং/অথবা আউটপুট হিসেবে tibble তৈরি করে।
এই কনসেপ্টটি শুধু একটি সংক্ষিপ্ত পরিচয় দিল, যা সম্ভব তার সবেমাত্র শুরু।
পরের কনসেপ্টগুলোতে ডেটাফ্রেমের আরও কয়েকটি দিক নিয়ে আলোচনা হবে (Exercism-এর কারিগরি সীমাবদ্ধতার মধ্যে)।