<- 5
a <- 3
b <- a + b
a_plus_b a_plus_b
[1] 8
בפרק זה נלמד את הבסיס של R, בעיקר נכיר את הפקודות הבסיסיות, אופרטורים (תנאים לוגיים) שונים, התניות, לולאות, סוגי משתנים ובניית פונקציות. בסיס זה נקרא הרבה פעמים גם Base R משום שהוא אינו מכיל חבילות הרחבה כלשהן, ומגיע עם התקנה חדשה של R.
פרק זה הוא בין הפרקים הבודדים בספר שמתמקד בעקרונות של תכנות כמו התניות, לולאות, ופונקציות. עקרונות אלו יהיו מוכרים מאוד למי שכבר למד תכנות, אך ייתכן שיהיו קצת יותר מאתגרים למישהו שמעולם לא למד תכנות. זה לא נורא, משום שהיכרות בסיסית עם העקרונות ומעט תרגול (כמו בתרגילים שמופיעים בפרק זה) יאפשרו גם למי שאין לו היכרות עם תכנות להתקדם לפרקים הבאים.
כדי לתרגל את הפקודות שתלמדו בפרק זה (ובפרקים הבאים) מומלץ לפתוח חלון של RStudio ולנסות את הפקודות השונות תוך כדי שאתם קוראים את הפרק.
ניתן להריץ ב-R פעולות אריתמטיות (חיבור, חיסור, כפל, חילוק), פונקציות, ולהגדיר משתנים שונים. לדוגמה, הקוד הבא מגדיר משתנה a
משתנה b
ומכניס את הסכום שלהם למשתנה חדש שיקרא a_plus_b
.
<- 5
a <- 3
b <- a + b
a_plus_b a_plus_b
[1] 8
שימו לב שההשמה לתוך משתנה מתבצעת עם האופרטור ->
, ניתן גם להשתמש ב=
לצורך השמה, כתיב זה פחות נפוץ. לדוגמה:
= a + b # this form of assignment `=` is less common, don't use it (use `<-`) a_plus_b
קודם השתמשנו בשמות a
, b
, ו-a_plus_b
כדי לקבוע משתנים. ככלל, מומלץ להשתמש בשמות קצרים בעלי משמעות. שמות משתנים חייבים להתחיל באות באנגלית, ויכולים להכיל אותיות, מספרים, קו תחתון, ונקודה. לדוגמה gender
, age
, raw_data
, וכו’.
בבסיס השפה יש כמה סוגי משתנים, שקובעים מה סוג הערכים שהמשתנה יכול לקבל:
מספר שלם (Integer)
מספר רציף (Double)
מחרוזת (Character)
משתנה קטגוריות (Factor)
תאריך (Date)
משתנה לוגי (Logical)
כל משתנה חדש אנחנו מגדירים ב-R הוא למעשה וקטור. אגב, גם כשאנחנו מגדירים משתנה כערך בודד, בעצם הוא וקטור עם איבר אחד. אנחנו יכולים להשתמש בפקודה c
(קיצור של המילה combine) כדי לשלב וקטורים שונים.
נראה דוגמאות להגדרות של וקטורים מסוגים שונים.
<- c(1L, 2L, 3L) # The L sign stands for "Long integer"
some_integer some_integer
[1] 1 2 3
<- c(1, 2, pi, exp(1))
some_double some_double
[1] 1.000000 2.000000 3.141593 2.718282
<- c("This", "is", "a", "character", "vector")
some_character some_character
[1] "This" "is" "a" "character" "vector"
<- factor(c("Apples", "Oranges", "Paers", "Mangos", "Apples", "Oranges"))
some_factor some_factor
[1] Apples Oranges Paers Mangos Apples Oranges
Levels: Apples Mangos Oranges Paers
<- c(Sys.Date(), as.Date("1993-08-01"))
some_date some_date
[1] "2024-10-06" "1993-08-01"
<- c(TRUE, FALSE, FALSE, TRUE) # can also use c(T, F, F, T) is the same
some_logical some_logical
[1] TRUE FALSE FALSE TRUE
למשתני קטגוריות יש שימוש חשוב בסטטיסטיקה שעוד נראה אותו בפרקים הבאים, ולכן הוא מובחן ממשתנה מחרוזת ומקבל מקום של כבוד. כפי שניתן לראות, כאשר מדפיסים אותו, R מדווח גם על הרמות השונות שכלולות בו.
שימוש בפקודה typeof(some_variable)
יציג את סוג המשתנה.
typeof(some_integer)
[1] "integer"
typeof(some_double)
[1] "double"
typeof(some_character)
[1] "character"
typeof(some_date)
[1] "double"
typeof(some_factor)
[1] "integer"
typeof(some_logical)
[1] "logical"
ניתן לשים לב ש-R מחשיב את המשתנה הקטגוריאלי כמספר שלם (integer) ואת התאריך כמספר רציף (double).
באמצעות הפקודה c והפקודה typeof בדקו מה קורה כאשר מחברים משתנים מסוגים שונים אחד לשני. האם התוצאה הגיונית? מה ההיגיון? האם יש מקרים בהם התוצאה של חיבור משתנים עשויה להטעות?
בדקו את c(some_factor, some_character)
ודוגמאות נוספות.
ב-R ניתן לקרוא לחלק מסוים מתוך וקטור. לדוגמה, אם אנחנו רוצים רק את שני האיברים הראשונים מתוך הוקטור some_factor או את האיבר הראשון והרביעי מתוך some_character נשתמש בכתיב:
1:2] some_factor[
[1] Apples Oranges
Levels: Apples Mangos Oranges Paers
c(1,4)] some_character[
[1] "This" "character"
כעת נדון במבנה נתונים שנקרא רשימה (list). רשימה היא אובייקט מרכזי ב-R שמאפשר לנו לאחד משתנים ווקטורים מסוגים שונים, לתוך dataset שיאפשר לנו בהמשך לנתח נתונים. ישנן מספר דרכים להגדיר רשימה, אחת מהן באמצעות הפקודה list
. לדוגמה, הרשימה הבאה תכיל את כל הוקטורים שהגדרנו עד כה, מבלי שהם יאבדו מהמשמעות שלהם (כפי שקורה כשמנסים לעשות חיבור רגיל).
<- list(my_int = some_integer,
my_list my_double = some_double,
my_character = some_character,
my_factor = some_factor,
my_date = some_date)
my_list
$my_int
[1] 1 2 3
$my_double
[1] 1.000000 2.000000 3.141593 2.718282
$my_character
[1] "This" "is" "a" "character" "vector"
$my_factor
[1] Apples Oranges Paers Mangos Apples Oranges
Levels: Apples Mangos Oranges Paers
$my_date
[1] "2024-10-06" "1993-08-01"
typeof(my_list)
[1] "list"
כדי לקרוא לוקטור מסוים מתוך רשימה ניתן להשתמש ב-$ באופן הבא:
$my_int my_list
[1] 1 2 3
החלק הסופי בהצגה שלנו הוא רשימה מסוג מאוד מסוים, data.frame. מבנה נתונים זה הוא רשימה שבה כל הוקטורים באותו האורך. הוקטורים יכולים להיות מסוגים שונים כפי שציינו, ומה שחשוב ב-data.frame הוא שהוא הולך להיות אבן הפינה שלנו בכל ניתוח נתונים סטטיסטי. בינתיים נסתפק בהדגמה קצרה של הגדרת data.frame אך נרחיב עליו בפרקים הבאים.
<- data.frame(name = c("Danny", "Moshe", "Iris", "Ronit"),
my_data favorite_fruit = factor(c("Mango", "Apple", "Apple", "Paer")),
age = c(25L, 32L, 22L, 30L),
height = c(1.8, 1.75, 1.6, 1.68),
married = c(F, T, F, T))
my_data
name favorite_fruit age height married
1 Danny Mango 25 1.80 FALSE
2 Moshe Apple 32 1.75 TRUE
3 Iris Apple 22 1.60 FALSE
4 Ronit Paer 30 1.68 TRUE
typeof(my_data)
[1] "list"
ניתן גם להפעיל פונקציות שונות, לדוגמה לוגריתם, פונקציות טריגונומטריות. למעשה בסעיף הקודם כבר ראינו מספר פונקציות כגון c
ו-typeof
. כעת נראה עוד מספר דוגמאות.
נסו להריץ את הקוד הבא, ולאחר מכן לענות על השאלות שלאחר מקטע הקוד. יש לשים לב שעל מנת להריץ את הפקודות בסוף המקטע (שקשורות ב-my_data
נדרש קודם להגדיר את my_data
כפי שהוגדר במקטע הקוד הקודם.
log(100) # natural logarithm
log10(100) # base 10 logarithm
sin(pi) # sin(pi) is 0 but may give you a surprising answer, why?
sqrt(4) # square root of 4
mean(my_data$age)
sd(my_data$age)
summary(my_data)
שאלה למחשבה: בחלק מהמחשבים התשובה שמתקבלת ל-sin(pi)
שונה מ-0.
לדוגמה
> sin(pi)
[1] 1.224606e-16
למה לדעתך?
summary
הפקודה האחרונה שהרצנו בדוגמה היא פקודת summary. מה עושה הפקודה summary עבור כל סוג עמודה שהיא מוצאת בdata.frame?
ככלל, הפעלת פונקציה ב-R תיראה כך:
# some code which defines the variable `bar` and then
<- some_function(foo = bar)
some_result
# or simply
<- some_function(bar) some_result
כאשר some_result
יחזיק את התוצאה של הפונקציה. הפונקציה עצמה נקראת some_function
, היא מקבלת ארגומנט (משתנה) יחיד שנקרא foo
ואנחנו משתמשים במשתנה שערכו bar
שנכנס לארגומנט.
כדי להמחיש נראה דוגמה נוספת, הפעם עם פונקציה פשוטה שגם נגדיר בעצמנו. נסו לעיין בקוד ולהבין מה המשמעות של כל שורה בקוד. שלושת השורות הראשונות בקוד מגדירות פונקציה חדשה, וההמשך מריץ אותה.
# define a new function which adds a number
<- function(number){
one_plus + 1
number
}# use the function:
one_plus(1)
[1] 2
one_plus(one_plus(1))
[1] 3
אופרטורים משמשים כדי להגדיר תנאים לוגיים שונים, לדוגמה אם אנחנו רוצים לבדוק את נכונותם של שני תנאים או יותר. ב-R נשתמש בתו כפול ||
כדי לציין “או” לוגי (or), ונשתמש בתו כפול &&
על מנת לציין “וגם” לוגי (and).
<- 5
a <- 6
b
< 3) && (b >= 3) (a
[1] FALSE
>= 5) || (b > 10) (a
[1] TRUE
ישנם גם פעולות לוגיות וקטוריות: כמו שניתן לחבר שני וקטורים, ניתן גם לבצע פעולות לוגיות איבר-איבר. פעולות אלו מבוצעות על ידי תו בודד: |
או &
.
<- c(T, T, F, F)
v1 <- c(T, F, T, F)
v2
| v2 v1
[1] TRUE TRUE TRUE FALSE
& v2 v1
[1] TRUE FALSE FALSE FALSE
||
ו-&&
נסו לבחון מה קורה במקרה של v1 || v2
או v1 && v2
. מה החוקיות?
אופרטור נוסף הוא אופרטור השלילה (not), נשתמש בתו !
על מנת לייצג אותו. לדוגמה:
!c(T, F)
[1] FALSE TRUE
!v1
[1] FALSE FALSE TRUE TRUE
==5 a
[1] TRUE
!(a==5)
[1] FALSE
שימו לב שבדוגמה האחרונה השתמשנו באופרטור נוסף אשר בודק אם שני אובייקטים הם בעלי אותו הערך. אופרטור זה מצויין עם שיוויון כפול ==
. ניתן גם להשוות שני משתנים זה לזה או להשוות שני וקטורים (איבר-איבר) באופן הבא:
== b a
[1] FALSE
c(1, 2, 3) == c(2, 1 , 3)
[1] FALSE FALSE TRUE
ישנם אופרטורים נוספים ב-R, אך בינתיים נסתפק באופרטורים שצוינו לעיל. כעת, לאחר שלמדנו על אופרטורים לוגיים, באפשרותנו ללמוד על התניות (if cluases) ועל לולאות (loops).
במקרים רבים אנחנו רוצים להתנות פעולות מסוימות בקיומו של תנאי כלשהו. ניתן לבצע התניה זו באמצעות הפקודות if
, else if
, else
.
המבנה הכללי של התניות יראה כך:
if (condition1) {
# some code which evaluates if condition1 == TRUE
else if (condition2) {
} # some code which evaluates if condition1 == FALSE and condition2 == TRUE
else {
} # some code which evaluates if condition1 == FALSE and condition2 == FALSE
}
על מנת להדגים, נשתמש בדוגמה הבאה: נניח שאנחנו רוצים לבדוק אם ערך מסוים הוא מספר או לא. נוכל להשתמש בשילוב של if
והפונקציה is.numeric
:
<- 100
some_value
if (is.numeric(some_value)) {
cat("This is indeed a numeric value!")
else {
} cat("This is not a numeric value!")
}
This is indeed a numeric value!
<- "foobar"
some_value
if (is.numeric(some_value)) {
cat("This is indeed a numeric value!")
else {
} cat("This is not a numeric value!")
}
This is not a numeric value!
אפשר גם להשתמש בהתניות בתוך התניות, כלומר בתוך פקודת if
להגדיר פקודת if
נוספת.
נסו להרחיב את הדוגמה הקודמת, כך שבמידה ו-some_value
הוא מספר המתחלק ב-2 אז יודפס “some_value is even” ובמידה ואינו מתחלק ב-2 יודפס “some_value is odd”.
נסו למצוא יותר מדרך אחת לביצוע הרחבה זו.
לולאות הן דרך נוחה כדי לגרום למחשב לעשות הרבה חזרות של אותה הפעולה (רק בשינוי ערכים מסוימים). ב-R ישנם סוגים שונים של לולאות, המתאפיינות בפקודות שונות אבל גם בזמן ריצה (יעילות) שונה.
for
while
repeat
break
next
lapply
, או פקודות מחבילות אחרות כמו map
שנמצאת בחבילת purrr
)עדיף להימנע ככל שניתן משימוש בכל סוגי הלולאות for
, while
, ו-repeat
בעבודה עם R
. לולאות אלו מאוד לא יעילות וזמן הריצה שלהן ארוך. תכנות פונקציונלי הוא יותר יעיל, אך דורש קצת יותר “התרגלות”. בכל זאת, נדגים את אופן הפעולה של לולאת for
.
על מנת להגדיר לולאת for
עלינו להגדיר ראשית את טווח הפעולה של הלולאה. לדוגמה, לולאה שרצה על המספרים 1 עד 100 תוגדר באופן הבא:
for (i in 1:100){
# do some action
# you can use i for that but don't have to
}
טווח הפעולה של הלולאה לא חייב להיות מספרי, אפשר גם להשתמש באובייקטים נוספים. לדוגמה נשתמש בלולאה על סוגי פירות. בדוגמה הבאה נשלב גם שימוש בהתניות, כפי שלמדנו בסעיף הקודם. השילוב של לולאות והתניות די נפוץ בתכנות.
<- c("Mango", "Bannana", "Pineapple", "Orange", "Apple", "Prune", "Lemon", "Loquat")
fruits <- c("Mango", "Orange", "Lemon", "Loquat")
my_garden
for (current_fruit in fruits) {
if (current_fruit %in% my_garden) {
cat("\nI grow", current_fruit)
else {
} cat("\nI don't grow", current_fruit)
} }
I grow Mango
I don't grow Bannana
I don't grow Pineapple
I grow Orange
I don't grow Apple
I don't grow Prune
I grow Lemon
I grow Loquat
%in%
לבין in
בדוגמה הקודמת השתמשנו ב-in
וגם ב-%in%
.
in
נחשבת מילת מפתח, בעוד ש-%in%
נחשבת אופרטור. עמדו על ההבדלים ביניהן וכתבו מה המשמעות של כל אחת, ומה התפקיד שלה בקוד.
נדגים גם כיצד ניתן להחליף לולאה באמצעות פעולה וקטורית, ובאמצעות נוסחה מתמטית. נניח שאנחנו רוצים לחשב סכום של טור הנדסי, כלומר הסכום של \(1, q, q^2, q^3, \ldots\) כאשר \(q<1\). נציג שלוש דרכים לבצע את הפעולה הזו: לולאה (הדרך הכי פחות יעילה), פעולה וקטורית (להיעזר בוקטור, כלומר מערך של מספרים), והדרך היעילה ביותר שהיא כמובן פשוט להשתמש בנוסחה של טור הנדסי (\(\frac{1}{1-q}\)).
<- 1/2
q
<- 0
series_sum_loop
for (element_i in 0:50){
<- series_sum_loop + q^element_i
series_sum_loop
}
series_sum_loop
[1] 2
<- sum(q^(0:50))
series_sum_vector
series_sum_vector
[1] 2
<- 1/(1-q)
series_sum_analytic
series_sum_analytic
[1] 2
חישוב של טור הנדסי הוא כמובן פעולה פשוטה, אבל לא בכל פעולה שנרצה לעשות תהיה נוסחה סגורה כמו שיש שלנו במקרה של טור הנדסי, וגם לא תמיד יהיה אפשר לבצע את הפעולה כפעולה וקטורית.
סדרת פיבונצ’י היא סדרה של מספרים שבה כל איבר הוא הסכום של שני האיברים שקדמו לו. שני האיברים הראשונים מקבלים את הערך 1, ולכן האיברים הראשונים של הסדרה נראים כך:
\[ 1, 1, 2, 3, 5, 8, 13, 21, 34,\ldots \] היעזרו בקוד הבא על מנת לבנות לולאה שתדפיס את 50 האיברים הראשונים של סדרת פיבונצ’י. עליכם להשלים את הקוד במקומות בהם מופיע סימן שאלה.
# Fibonachi code exercise, fill in the blanks (where you see `?`)
<- ?
total
<- 1
element_i_minus1 <- 1
element_i_minus2
for (? in 3:total){
<- element_i_minus1 + ?
next_element <- element_i_minus1
element_i_minus2 <- next_element
?
cat("\n", ?)
}
בפרק זה למדנו את התחביר הבסיסי של שפת R
.
ראינו כיצד עובדת השמת משתנים
למדנו על סוגי משתנים שונים
ראינו כיצד לקרוא לתתי וקטורים (חלק מוקטור)
למדנו על אובייקטים מסוג רשימה (list)
למדנו על פונקציות שונות - כיצד להפעיל אותן וכיצד להגדיר פונקציות חדשות
למדנו על אופרטורים (וגם, או, לא - NOT, והשוואה)
למדנו על התניות (אם… אז… אחרת)
למדנו על לולאות (מסוג for
), והזכרנו סוגים נוספים.
עד כה הכלים שתיארנו הם כלים כלליים, במובן שבמרבית שפות התכנון ישנן מקבילות דומות. בפרק הבא נתעמק בכלים ייעודיים אשר נבנו ומשמשים לניתוח נתונים סטטיסטי (או ליתר דיוק בהכנת נתונים לניתוח נתונים סטטיסטיקה).
המדריך העברי למשתמש ב-R נכתב על ידי עדי שריד בהוצאת מכון שריד