সমজাতীয় অ্যাটমিক ডেটার সংগ্রহ নিয়ে কাজ করার সময় (যেমন সবগুলো সংখ্যা বা সবগুলো ক্যারেক্টার), R-এ নিচের স্ট্রাকচারগুলো ব্যবহার করা যায়:
vector, যা একটি ইনডেক্স দিয়ে অ্যাক্সেস করা হয়।matrix.array. একটি matrix আসলে N=2 সহ একটি array মাত্র।Python প্রোগ্রামারদের জন্য: এই অংশটি চেনা চেনা লাগতে পারে।
NumPy-এর সাথে যে মিল আছে, সেটি আকস্মিক নয় (তবে R একটু আগের, আর এই সবকিছু এর বেস ভাষাতেই আছে)।
একটি ম্যাট্রিক্স আসলে একটি ভেক্টরই, সাথে আকৃতি নির্ধারণের জন্য সারি ও কলামের সংখ্যা।
এগুলো তৈরি করার জন্য একটি matrix() ফাংশন আছে:
# data already available
matrix(1:6, nrow=2, ncol=3)
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
# empty matrix to be filled in later
matrix(nrow=2, ncol=3)
#> [,1] [,2] [,3]
#> [1,] NA NA NA
#> [2,] NA NA NA
খেয়াল করুন, ডেটা কলাম-মেজর অর্ডারে সংরক্ষিত থাকে (অর্থাৎ আগে নিচে, তারপর পাশে)। এটি Fortran, Matlab ও Julia-র মতোই, কিন্তু C/C++ ও NumPy থেকে আলাদা।
সারি ও কলাম দুটোর জন্যই ম্যাট্রিক্সের ডাইমেনশন জানতে dim(), আর একটি মাত্র ডাইমেনশন পেতে nrow() বা ncol() ব্যবহার করুন। length() ব্যবহার করলে কেবল অন্তর্নিহিত ভেক্টরের দৈর্ঘ্যই ফেরত আসবে: মোট এলিমেন্টের সংখ্যা (উপরের উদাহরণে 6)।
আরও কিছু ফাংশন ম্যাট্রিক্স আউটপুট তৈরি করে, যেমন str_split_fixed().
library(stringr)
str_split_fixed(c("split some words", "and some more"), " ", 3)
#> [,1] [,2] [,3]
#> [1,] "split" "some" "words"
#> [2,] "and" "some" "more"
এটি ভেক্টর ইনডেক্সিংয়ের সব উপায়েই কাজ করে, শুধু সারি ও কলাম যথাক্রমে উল্লেখ করতে হয়। পুরো সারি বা কলাম পেতে একটি ডাইমেনশন খালি রাখুন।
উপরের একই ম্যাট্রিক্স ব্যবহার করে:
m <- matrix(1:6, nrow=2, ncol=3)
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m[2, 3] # row 2, col 3
#> [1] 6
m[1:2, 3] # rows 1:2, col 3
#> [1] 5 6
m[2, ] # all of row 2
#> [1] 2 4 6
খেয়াল করুন, একটিমাত্র সারি বা কলামের সাবসেট ভেক্টর হিসেবে ফেরত আসবে, ম্যাট্রিক্স হিসেবে নয়।
class(m)
#> [1] "matrix" "array"
class(m[2,])
#> [1] "integer"
এই ধরনের dimension reduction সব সময় কাঙ্ক্ষিত নয়, বিশেষ করে এমন ফাংশন পাইপলাইনে যা ম্যাট্রিক্স ইনপুট প্রত্যাশা করে।
drop=FALSE উল্লেখ করলে ম্যাট্রিক্স ইনপুট থেকে ম্যাট্রিক্স রিটার্ন ভ্যালু নিশ্চিত হবে। খেয়াল করুন, কেবল একটি সারি বা একটি কলামবিশিষ্ট ম্যাট্রিক্স আর ভেক্টর একই টাইপ নয়।
m[2, , drop = FALSE]
#> [,1] [,2] [,3]
#> [1,] 2 4 6
as.matrix() ফাংশন একটি ভেক্টরকে এক-কলামের ম্যাট্রিক্সে রূপান্তর করে।
as.matrix(1:3)
#> [,1]
#> [1,] 1
#> [2,] 2
#> [3,] 3
ভেক্টরের মতোই, নতুন মান অ্যাসাইন করে ম্যাট্রিক্সের অংশবিশেষ পরিবর্তন করা যায়।
m[, 2] <- c(10, 20)
m
#> [,1] [,2] [,3]
#> [1,] 1 10 5
#> [2,] 2 20 6
ভেক্টরের ক্ষেত্রে ডেটা ঢোকাতে ও যোগ করতে c() ফাংশনই যথেষ্ট ছিল। ম্যাট্রিক্সের গঠন বেশি, তাই কী দরকার তা নির্দিষ্ট করতে বেশি যত্ন লাগে।
rbind() ও cbind() ফাংশন ম্যাট্রিক্সের সাথে নতুন সারি বা কলাম যুক্ত করে।
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m2 <- matrix(7:9, nrow = 1, ncol = 3)
rbind(m, m2)
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
#> [3,] 7 8 9
ভেক্টরের মতোই, সারি বা কলাম মুছতে ঋণাত্মক ইনডেক্সিং ব্যবহার করা হয়।
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m[, -2]
#> [,1] [,2]
#> [1,] 1 5
#> [2,] 2 6
সব ভেক্টর ফাংশনই ম্যাট্রিক্সে কাজ করে, সব এলিমেন্টের উপর প্রয়োগ হয়ে। তবে তারা ম্যাট্রিক্সের গঠন হারিয়ে ভেক্টর ফেরত দিতে পারে।
sqrt(m)
#> [,1] [,2] [,3]
#> [1,] 1.000000 1.732051 2.236068
#> [2,] 1.414214 2.000000 2.449490
sum(m)
#> [1] 21
m[m > 3]
#> [1] 4 5 6
ম্যাট্রিক্সের জন্য colSums() ও rowSums() ফাংশনও কাজে লাগতে পারে। এখানে ডাইমেনশন রিডাকশনই কাঙ্ক্ষিত, তাই রিটার্ন ভ্যালুগুলো ভেক্টর।
# m as defined above
rowSums(m)
#> [1] 9 12
colSums(m)
#> [1] 3 7 11
একইভাবে, গড় নির্ণয়ের জন্য rowMeans() ও colMeans() ফাংশন আছে।
অন্য কনসেপ্টে আমরা দেখেছি, Tidyverse লাইব্রেরিগুলো ভেক্টর ও অ্যারে (purrr) বা ডেটাফ্রেম (dplyr) নিয়ে কাজ সহজ করতে পারে।
তবে ম্যাট্রিক্স ও অ্যারে বেশিরভাগ ক্ষেত্রেই Tidyverse ইনপুটের আওতার বাইরে (অন্তত এখন তো বটেই)।
apply() ফাংশন Base R-এর অংশ, আর ম্যাট্রিক্স প্রোগ্রামিংয়ের সূক্ষ্ম নিয়ন্ত্রণের জন্য এখনও সেরা হাতিয়ার।
প্যারামিটারগুলো ক্রমানুসারে:
rowMeans() ফাংশন Base R-এ আছে, তবে apply() ও mean() দিয়েও এটি নকল করা যায়।
randm <- sample(1:10, 16, replace = TRUE) |> matrix(4, 4)
randm
#> [,1] [,2] [,3] [,4]
#> [1,] 4 9 8 3
#> [2,] 6 8 5 5
#> [3,] 4 2 1 2
#> [4,] 4 2 5 4
apply(randm, 1, mean)
#> [1] 6.00 6.00 2.25 3.75
rowMeans(randm)
#> [1] 6.00 6.00 2.25 3.75
এই অংশটি একধরনের গাণিতিক জ্ঞান ধরে নেয়, যা Exercism-এর পরিধির বাইরে।
এটি আপনার কাছে দুর্বোধ্য লাগলে অনুগ্রহ করে এড়িয়ে যান।
তবে R একটি ডেটা সায়েন্স ভাষা, আর অনেক ব্যবহারকেরই ফলিত গণিত ও স্ট্যাটিস্টিকসের পটভূমি আছে। এই সংক্ষিপ্ত সারসংক্ষেপ কিছু শিক্ষার্থীর কাজে লাগতে পারে।
আধুনিক R "স্পষ্টতই" একটি ডেটা সায়েন্স ভাষা: মূলত টেবুলার ডেটা নিয়ে কাজ করার জন্য তৈরি, আর স্ট্যাটিস্টিশিয়ান, এপিডেমিওলজিস্ট ও সমাজবিজ্ঞানীদের মতো ব্যবহারকারীদের মধ্যে জনপ্রিয়। এই ক্ষেত্রে এর প্রধান প্রতিদ্বন্দ্বী Python-এর সায়েন্টিফিক স্ট্যাক, যার মধ্যে Pandas বা Polars-এর মতো প্যাকেজ আছে।
তবে আগের দশকগুলোতে R-কে একটি সম্ভাব্য ইঞ্জিনিয়ারিং ভাষা হিসেবেও দেখা হতো, যা সরাসরি Matlab-এর সাথে প্রতিদ্বন্দ্বিতা করত।
আশ্চর্যজনকভাবে, Base R এখনও ভেক্টর ও ম্যাট্রিক্সে লিনিয়ার অ্যালজেবরার সম্পূর্ণ পরিসরের অপারেশন সমর্থন করে। এগুলো এখন ব্যবহারকারীর কোডে কতটা ব্যবহৃত হয় তা স্পষ্ট নয়, তবে স্ট্যাটিস্টিক্যাল ফাংশন বাস্তবায়নের জন্য এসব কৌশল ভেতরে খুবই গুরুত্বপূর্ণ থেকে যায়।
R-এ LinAlg এখনও কিছু কলেজ কোর্সে শেখানো হয়, যেমন এই ইকোনোমেট্রিক্স ক্লাসে. গণিতের (প্রোগ্রামিং নয়) একটি ভালো নির্দেশিকা আছে 3Blue1Brown-এ।
Exercism-এর মধ্যে লিনিয়ার অ্যালজেবরার সবচেয়ে পূর্ণাঙ্গ আলোচনা রয়েছে Julia সিলেবাসে (R সিলেবাসের লেখকদেরই লেখা)। আমাদের বিশ্বাস, গুরুতর লিনিয়ার অ্যালজেবরার কাজে R-এর চেয়ে Julia (বা যেখানে numpy.linalg মডিউল পাওয়া যায় সেখানে Python) এখন বেশি উপযুক্ত ভাষা হবে।
একটি বর্গ (N x N)) ম্যাট্রিক্স A-এর প্রধান ডায়াগোনাল উপরের বাম (A[1,1]) থেকে নিচের ডানে (A[N,N]) পর্যন্ত বিস্তৃত।
diag() ফাংশনটি তার কাছে পাঠানো আর্গুমেন্টের উপর নির্ভর করে ভিন্নভাবে আচরণ করে।
একটি ভেক্টর v দিলে diag(v) একটি ডায়াগোনাল ম্যাট্রিক্স তৈরি করে।
diag(1:3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 2 0
#> [3,] 0 0 3
একটি বর্গ ম্যাট্রিক্স A দিলে diag(A) প্রধান ডায়াগোনালের এলিমেন্টগুলো একটি ভেক্টর হিসেবে ফেরত দেয়।
A
#> [,1] [,2] [,3]
#> [1,] 10 5 2
#> [2,] 9 3 7
#> [3,] 8 10 4
diag(A)
#> [1] 10 3 4
একটি মাত্র ইন্টিজার k দিলে diag(k) একটি k × k আইডেন্টিটি ম্যাট্রিক্স তৈরি করে: সম্ভবত আপনি যা অনুমান করেছিলেন তা নয়।
এমনকি গুরুতর R পাঠ্যবইও এখানে "ভাবুন তো"-এর মতো মন্তব্য যোগ করে।
diag(3)
#> [,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 1 0
#> [3,] 0 0 1
সারি ও কলাম বদলাতে t() ফাংশন যেকোনো ম্যাট্রিক্স ট্রান্সপোজ করে।
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
t(m)
#> [,1] [,2]
#> [1,] 1 2
#> [2,] 3 4
#> [3,] 5 6
জটিল-মানের ম্যাট্রিক্সের ক্ষেত্রে ফলাফল জটিল কনজুগেট নয়। ম্যাট্রিক্স A-এর কনজুগেট ট্রান্সপোজ (অ্যাডজয়েন্ট) পেতে Conj(t(A)) ব্যবহার করুন।
উচ্চতর মাত্রার অ্যারের জন্য aperm() ডাইমেনশন পারমিউট করে।
বর্গ ম্যাট্রিক্স A-এর ইনভার্সের জন্য আমরা solve(A) ব্যবহার করতে পারি। আমাদের অনেকের কাছেই এই সিনট্যাক্স ... অপ্রত্যাশিত লাগে।
একটি ম্যাট্রিক্সকে তার ইনভার্স দিয়ে গুণ করলে আইডেন্টিটি ম্যাট্রিক্স পাওয়া যায় (খুব ছোট রাউন্ডঅফ এরর সাপেক্ষে)।
A <- matrix(sample(1:10, 9, replace = TRUE), 3, 3)
A
#> [,1] [,2] [,3]
#> [1,] 10 5 2
#> [2,] 9 3 7
#> [3,] 8 10 4
solve(A)
#> [,1] [,2] [,3]
#> [1,] 0.16666667 0.00000000 -0.08333333
#> [2,] -0.05747126 -0.06896552 0.14942529
#> [3,] -0.18965517 0.17241379 0.04310345
A %*% solve(A) |> round(digits = 10)
[,1] [,2] [,3]
#> [1,] 1 0 0
#> [2,] 0 1 0
#> [3,] 0 0 1
+, -, *, ^-এর মতো সাধারণ গাণিতিক অপারেটরগুলো ম্যাট্রিক্সে এলিমেন্ট-ভিত্তিক কাজ করে। ডাইমেনশন না মিললে রিসাইক্লিংয়ের স্বাভাবিক নিয়মগুলো প্রযোজ্য হয়।
m
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
m * m
#> [,1] [,2] [,3]
#> [1,] 1 9 25
#> [2,] 4 16 36
# same calculation, different syntax
m ^ 2
#> [,1] [,2] [,3]
#> [1,] 1 9 25
#> [2,] 4 16 36
ভেক্টর ও ম্যাট্রিক্স গুণের অন্যান্য বিভিন্ন ধরন ব্যাখ্যা করা আমাদের পরিধির বাইরে, তবে খুব সংক্ষেপে:
%*% হলো matmult()-এর ইনফিক্স অপারেটর রূপ, যা ম্যাট্রিক্স গুণফল দেয়।%o% হলো outer()-এর ইনফিক্স অপারেটর রূপ, যা আউটার গুণফল দেয়।crossprod() ক্রস গুণফল দেয়।