مسیر
/
R
R
/
برنامه‌ی درسی
/
ماتریس و آرایه
ما

ماتریس و آرایه در R

{one: "۱ تمرین", other: "%{count} تمرین"}

درباره‌ی ماتریس و آرایه

هنگام کار با مجموعه‌هایی از داده‌های اتمی همگن (مثلاً همه عددی یا همه کاراکتری)، ساختارهای زیر در R در دسترس هستند:

  • vector برای داده‌های یک‌بعدی، که با یک اندیس به آن دسترسی پیدا می‌کنید.
  • matrix برای داده‌های دوبعدی و مستطیلی (نه دندانه‌دار).
  • array برای حالت کلی Nبعدی. یک matrix صرفاً یک array با N=۲ است.
Note

برای برنامه‌نویسان 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() فقط طول بردار زیربنایی را برمی‌گرداند: تعداد کل عناصر (۶ در مثال بالا).

برخی توابع دیگر خروجی ماتریسی تولید می‌کنند، مثلاً 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 است و هنوز بهترین ابزار برای کنترل دقیق‌تر برنامه‌نویسی ماتریس است.

پارامترها، به ترتیب، عبارت‌اند از:

  1. ماتریس ورودی.
  2. بُعدی که عملیات روی آن انجام می‌شود (۱ برای سطرها، ۲ برای ستون‌ها).
  3. تابعی که اعمال می‌شود.
  4. به‌صورت اختیاری، هر پارامتر اضافی برای تابع.

تابع 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

جبر خطی

Note

این بخش سطحی از دانش ریاضی را فرض می‌کند که خارج از محدوده‌ی Exercism است.

لطفاً اگر برایتان معنایی ندارد، از آن بگذرید.

با این حال، R یک زبان علم داده است و بسیاری از کاربران پیش‌زمینه‌ای در ریاضیات کاربردی و آمار دارند. این خلاصه‌ی مختصر ممکن است برای برخی دانشجویان جالب باشد.

R مدرن، «بدیهی است»، یک زبان علم داده است: برای کار عمدتاً با داده‌های جدولی طراحی شده و در میان کاربرانی مانند آماردانان، اپیدمیولوژیست‌ها و دانشمندان علوم اجتماعی محبوب است. رقابت‌کننده‌ی اصلی در این زمینه، پشته‌ی علمی Python است، شامل بسته‌هایی مانند Pandas یا Polars.

با این حال، در دهه‌های پیشین R نیز به‌عنوان یک زبان مهندسی بالقوه دیده می‌شد که مستقیماً با Matlab رقابت می‌کرد.

شاید جای تعجب باشد که Base R هنوز از طیف کاملی از عملیات جبر خطی روی بردارها و ماتریس‌ها پشتیبانی می‌کند. روشن نیست که این‌ها اکنون چقدر در کد کاربران نهایی به کار می‌روند، اما چنین تکنیک‌هایی برای پیاده‌سازی توابع آماری به‌صورت درونی بسیار مهم باقی می‌مانند.

جبر خطی در R هنوز در برخی دوره‌های دانشگاهی تدریس می‌شود، مانند این کلاس اقتصادسنجی. راهنمای خوبی برای ریاضیات (نه برنامه‌نویسی) در 3Blue1Brown وجود دارد.

Advanced

کامل‌ترین بحث جبر خطی در Exercism در سرفصل درسی Julia است (همان نویسندگان سرفصل R). ما معتقدیم که Julia (یا Python در زمینه‌هایی که ماژول numpy.linalg در دسترس است) اکنون زبان مناسب‌تری برای کار جدی جبر خطی نسبت به R خواهد بود.

قطرها

قطر اصلی یک ماتریس مربعی (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

برای یک ماتریس با مقادیر مختلط، نتیجه، مزدوج مختلط نیست. از Conj(t(A)) استفاده کنید تا ترانهاده‌ی مزدوج (الحاقی) ماتریس 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

توضیح انواع دیگر ضرب بردار و ماتریس خارج از محدوده‌ی این بحث است، اما به‌صورت خیلی مختصر:

ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود