بر

بردار در R

1 تمرین

درباره‌ی بردار

یک vector (از نظر فنی یک atomic vector) در R مجموعه‌ای از مقادیر از همان type است: معمولاً integer، numeric، character، logical؛ و به‌ندرت complex یا raw. این مقادیر به‌صورت پیوسته در حافظه ذخیره می‌شوند، مشابه آرایه‌ی C.

اگر ورودی‌های مخلوطی به آن داده شود، R بی‌سروصدا همه‌ی آن‌ها را به یک نوع مشترک، معمولاً character، تبدیل می‌کند که می‌تواند به نتایج غیرمنتظره‌ای منجر شود.

v <- c(2, 5.4, "txt")
v
#> [1] "2"   "5.4" "txt"
typeof(v)
#> [1] "character"

ساختن بردار

اگر همه‌ی مقادیر را از قبل در اختیار دارید، از c() (مخفف «concatenate») استفاده کنید:

x <- 10
v <- c(4, 7, x)

تابع c() بسیار انعطاف‌پذیر است و ورودی‌هایش می‌توانند شامل بردارها و بازه‌های دیگر باشند. c() هر تعداد و هر نوع ورودی را می‌پذیرد و همه را در یک بردار واحد مسطح می‌کند.

اگر بردار کوچک شروع شود و در زمان اجرا زیاد رشد کند (مثلاً با افزودن مقادیر در یک حلقه)، تخصیص از پیش یک بردار بلند که بعداً بتوان آن را با داده‌های واقعی پر کرد می‌تواند به‌مراتب سریع‌تر باشد:

v <- vector(length = 100) 

این کار نیاز به کپی کردن شیء را به کمترین حد می‌رساند: عملیاتی کند و پرمصرف از نظر حافظه برای بردارهای بلند.

بازه، دنباله، تکرار

برای یک بازه از مقادیر متوالی (صعودی یا نزولی) می‌توانید از نماد : استفاده کنید

v <- 1:5 # equivalent to c(1, 2, 3, 4, 5)
w <- 10:7 # c(10, 9, 8, 7)

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

تابع seq() کنترل دقیق‌تری ارائه می‌دهد:

# specify step size
seq(2, 11, by = 3) # c(2, 5, 8, 11)

# specify number of values, evenly-spaced
seq(2.5, 4, length.out = 5) # c(2.500, 2.875, 3.250, 3.625, 4.000) 

تابع rep() ساختن الگوهای تکراری را آسان می‌کند:

count <- 3
rep(7, count)
#> [1] 7 7 7

rep(1:3, count)
#> [1] 1 2 3 1 2 3 1 2 3

rep(1:3, each = 2) # the "each" keyword is required in this case
#> [1] 1 1 2 2 3 3

چند بازه به‌صورت ثابت‌های از پیش تعریف‌شده‌ی داخلی وجود دارند:

letters  # LETTERS is the UPPERCASE equivalent
#> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"

month.name
#> [1] "January"   "February"  "March"     "April"     "May"       "June"      "July"      "August"   
#> [9] "September" "October"   "November"  "December" 

month.abb
#> [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"

محاسبات

عملیات به‌طور خودکار به‌صورت عنصربه‌عنصر روی بردار اعمال می‌شوند. حلقه‌ها، درک فهرست و بازگشت‌هایی که در زبان‌های دیگر رایج‌اند اینجا لازم نیستند و (به دلایل کارایی) در صورت امکان باید از آن‌ها پرهیز کرد.

2 + 1:3     # c(3, 4, 5)
2 * 1:3     # c(2, 4, 6)
12 / 1:3    # c(12  6  4)
2 ^ (1:3)   # c(2, 4, 8) : exponentiation, operator precedence order here needs ()

جفت‌های بردار هم کار می‌کنند و عناصر به‌صورت جفتبه‌جفت پردازش می‌شوند:

v <- 1:3
w <- 5:7

v + w
#> [1]  6  8 10

v * w
#> [1]  5 12 21

v ^ w
#> [1]    1   64 2187

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

دسترسی به عناصر بردار

مانند بسیاری از زبان‌ها، می‌توانیم اندیس را داخل کروشه به کار ببریم:

v <- 4:7
v[2]
#> [1] 5
v[2:4]
#> [1] 5 6 7

اما مراقب باشید: R به‌طور پیش‌فرض از اندیس‌گذاری با مبنای ۱ استفاده می‌کند. این در زبان‌های علمی (Fortran، Matlab، Julia) رایج است، اما با اندیس‌گذاری با مبنای صفر در C و بسیاری از جانشینانش تفاوت دارد.

می‌توان از بازه‌ها استفاده کرد، همان‌طور که در مثال بالا دیدیم و همچنین در تابع بسیار انعطاف‌پذیر c():

v <- c(4, 7, 10)
w <- c(2, v[2:3], 11) # c(2, 7, 10, 11)

با vectors (مانند بیشتر موارد در R) می‌توان مانند یک ساختار قابل‌تغییر رفتار کرد و با نگارشی مشابه و انعطاف‌پذیر به یک یا چند عنصر مقدار نوشت:

v <- 1:10
v[2] <- 42
v[4:6] <- c(11, 12, 13)
v
#> [1]  1 42  3 11 12 13  7  8  9 10

اندیس‌های منفی

برنامه‌نویسانی که با زبان‌های دیگر آشنا هستند ممکن است گمان کنند v[-1] روشی برای دسترسی به آخرین عنصر یک بردار است. اشتباه است! برای این کار از tail() استفاده کنید.

v <- c(2, 4, 6) # defaults to last 6 entries
tail(v)
#> [1] 2 4 6
tail(v, n=1) # just the last entry
#> [1] 6

اندیس‌های منفی در واقع روشی برای حذف یک عنصر هستند:

v <- c(2, 4, 6)
v[-2]
#> [1] 2 6  # second element gone

این کار نسخه‌ای کوتاه‌تر از بردار برمی‌گرداند. خود v تغییری نمی‌کند.

شرطی‌های بردار

اعمال مقایسه‌ها روی یک بردار، برداری از مقادیر منطقی با همان طول برمی‌گرداند. این ویژگی داخل توابعی مانند all() و any() بسیار کارآمد است:

v <- c(4, 7, 10)
v >= 6
#> [1] FALSE  TRUE  TRUE
all(v > 6)
#> [1] FALSE    # not all elements match this condition
any(v > 6)
#> [1] TRUE     # at least one element matches

برای عبارت‌های منطقی پیچیده‌تر، استفاده از عملگرهای & و | که روی بردارها کار می‌کنند اهمیت دارد. عملگرهای دوکاراکتری && و || فقط برای مقایسه‌ی مقادیر تکی در نظر گرفته شده‌اند، همان‌طور که در [مفهوم منطقی][concept-boolean] بحث شد.

v <- c(4, 7, 10)
v %% 2 == 0 & v > 5
#> [1] FALSE FALSE  TRUE

بردارها در R همه‌جا هستند

این موضوعی است که در مباحث بعدی به آن بازخواهیم گشت. فعلاً فقط به قالب خروجی در کنسول توجه کنید:

1:20
#> [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17
#> [18] 18 19 20

هر خط با عددی داخل [ ] شروع می‌شود که اندیس اولین عنصر را نشان می‌دهد (برای بردارهای بلندی که در چند خط می‌شکنند مفید است). اما چرا مقادیر تکی با [1] شروع می‌شوند؟

Note

هیچ مقدار اسکالر واقعی‌ای در R وجود ندارد!

آنچه به نظر یک عدد یا حرف تکی می‌آید، در واقع برداری با طول ۱ است.

پیش‌تر نشانه‌هایی از این را در مفاهیم مبانی و منطقی با انواع عددی و منطقی دیده‌ایم.

نکته‌ای درباره‌ی نمادگذاری

بردارهایی که در بالا توصیف شد در اصطلاح رسمی‌تر atomic vectors نامیده می‌شوند. دلیلش این است که آن‌ها فقط می‌توانند مقادیر ساده و تقسیم‌ناپذیر (و از این رو «اتمی») مانند numeric، character، boolean را در خود جای دهند. ساختارهای پیچیده‌تر می‌توانند recursive vectors را تشکیل دهند که هنگام بحث درباره‌ی lists معرفی خواهند شد.

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

بردار را یاد بگیرید