1 Operators

# python
print(1. + 2.)
## 3.0
print(10.**2) # exponent
## 100.0
print(3 / 2)
## 1.5
print(3 // 2) # integer division
## 1
print(15 % 4) # modulo = remainder
## 3
# R
print(1 + 2)
## [1] 3
print(10 ^ 2) # exponent
## [1] 100
print(3 / 2)
## [1] 1.5
print(as.integer(3 / 2)) # there is no direct integer division
## [1] 1
print(15 %% 4) # modulo = remainder
## [1] 3

2 Order of operations

Most to least powerful. Use parentheses to ensure intended order of operations.

# python
print(100 + 1000/10**2)
## 110.0
print(100 + (1000/10)**2)
## 10100.0
print((100 + 1000/10)**2)
## 40000.0
# R
print(100 + 1000/10^2)
## [1] 110
print(100 + (1000/10)^2)
## [1] 10100
print((100 + 1000/10)^2)
## [1] 40000

3 Logical Comparisons

# python
a = 2
print(a)
## 2
print(3 > a) # greater
## True
print(3 < a) # smaller
## False
print(a == 2) # equals
## True
print(a != 2) # not equals
## False
# R
a <- 2
print(a)
## [1] 2
print(3 > a) # greater
## [1] TRUE
print(3 < a) # smaller
## [1] FALSE
print(a == 2) # equals
## [1] TRUE
print(a != 2) # not equals
## [1] FALSE
# NOTE: in R these comparisons also work for vectors and are applied on an entry-by-entry basis
a <- c(1, 2, 7)
print(a)
## [1] 1 2 7
print(3 > a)
## [1]  TRUE  TRUE FALSE
print(3 < a) 
## [1] FALSE FALSE  TRUE
print(a == 2) 
## [1] FALSE  TRUE FALSE
print(a != 2) 
## [1]  TRUE FALSE  TRUE

4 Chaining Logical Comparisons

# python --> keywords and, or
a = 2
b = 3
print((a == 2) and (b > 1))
## True
print((a != 2) or (b > 1))
## True
# R - operators &&, ||
a <- 2
b <- 3
print(a == 2 && b > 1)
## [1] TRUE
print(a != 2 || b > 1)
## [1] TRUE
# however, be careful chaining vectors with logical comparisons
a <- c(1, 2, 7)
print(a)
## [1] 1 2 7
print(a == 1) # vector of TRUE/FALSE values
## [1]  TRUE FALSE FALSE
print(a == 1 && b > 1) # only 1st value of vector is used in && chain!!
## [1] TRUE
print(a == 1 & b > 1) # use & and | instead for element-wise chaining 
## [1]  TRUE FALSE FALSE
# also possible for multi-vector chains such as in this data table
dplyr::data_frame(
  a = c(T, F, F, T),
  b = c(F, T, T, T),
  a_and_b = a & b,
  a_or_b = a | b,
  a_not_b = a != b
)
## # A tibble: 4 x 5
##   a     b     a_and_b a_or_b a_not_b
##   <lgl> <lgl> <lgl>   <lgl>  <lgl>  
## 1 TRUE  FALSE FALSE   TRUE   TRUE   
## 2 FALSE TRUE  FALSE   TRUE   TRUE   
## 3 FALSE TRUE  FALSE   TRUE   TRUE   
## 4 TRUE  TRUE  TRUE    TRUE   FALSE

5 Bitwise operators

# python
BACON = 1     # 0001 in binary
LETTUCE = 2   # 0010 in binary
TOMATO = 4    # 0100 in binary
SOURDOUGH = 8 # 1000 in binary
sandwich = BACON | TOMATO | SOURDOUGH  # = 1101 in binary (all but lettuce)
print(sandwich)
# what bits does the sandwich have in common with each ingredient?
## 13
print(sandwich & BACON) # the BACON bit is also 1 in the sandwich
## 1
print(sandwich & LETTUCE) # the LETTUCE bit is 0 in the sandwich
## 0
print(sandwich & TOMATO) # the TOMATO bit is 1 in the sandwich 
## 4
print(sandwich & SOURDOUGH) # the SOURDOUGH bit is 1 in the sandwich
## 8
# R 
# CAUTION: remember that & and | are _ELEMENT-wise_ operators for vectors, 
# NOT bitwise operators like in python - if bitwise is needed, there are 
# specific functions defined for it
BACON <- 1     # 0001 in binary
LETTUCE <- 2   # 0010 in binary
TOMATO <- 4    # 0100 in binary
SOURDOUGH <- 8 # 1000 in binary
sandwich <- bitwOr(bitwOr(BACON, TOMATO), SOURDOUGH)
print(sandwich)
## [1] 13
print(bitwAnd(sandwich, BACON))
## [1] 1
print(bitwAnd(sandwich, LETTUCE))
## [1] 0
print(bitwAnd(sandwich, TOMATO))
## [1] 4
print(bitwAnd(sandwich, SOURDOUGH))
## [1] 8

6 Functions

6.1 Return values

# python
def foo1():
  pass
print(foo1()) # if no return value is defined, return value is None
## None
def foo2():
  return(1)
print(foo2()) # one return value
## 1
def foo3():
  return(1, "two", 3.14)
print(foo3()) # multiple return values
## (1, 'two', 3.14)
# R
foo1 <- function() {
}
str(foo1()) # if no return value defined, return value is NULL
##  NULL
# BUT: last statement of a function becomes automatically the return value!!
# --> good coding = be explicit and always use return() statements in a function
foo1b <- function() {
  1
}
str(foo1b()) 
##  num 1
foo2 <- function() {
  return(1)
}
str(foo2()) # one return value
##  num 1
foo3 <- function() {
  return(list(1, "two", 3.14))
}
str(foo3()) # multiple return values
## List of 3
##  $ : num 1
##  $ : chr "two"
##  $ : num 3.14

6.2 Parameters (or Arguments)

# python
def foo4(x, y):
  return(x * y**2)
print(foo4(2, 5)) # parameters passed in order
## 50
print(foo4(y = 2, x = 5)) # passed by explicit name
# parameter defaults
## 20
def foo5(x, y = 5):
  return (x * y**2)
print(foo5(2)) # use with default for y
## 50
print(foo5(2, 10)) # overwrite default for y
# variables as parameters
## 200
a = 4
print(foo5(x = a)) # x takes the value of a inside the function
# cannot pass undefined variables
## 100
try:
  foo5(x = undef)
except:
  print("Unexpected error:", sys.exc_info()[1])
  
# cannot omit parameters that don't have defaults, even if they are not used
## Unexpected error: name 'undef' is not defined
def foo6(x, y = 5):
  return(y**2) # x not used!
  
try:
  foo6()
except:
  print("Unexpected error:", sys.exc_info()[1])
## Unexpected error: foo6() missing 1 required positional argument: 'x'
# R
foo4 <- function(x, y) {
  return(x * y^2)
}
print(foo4(2, 5)) # parameters passed in order
## [1] 50
print(foo4(y = 2, x = 5)) # passed by explicit name
## [1] 20
# parameter defaults
foo5 <- function(x, y = 5) {
  return (x * y^2)
}
print(foo5(2)) # use with default for y
## [1] 50
print(foo5(2, 10)) # overwrite default for y
## [1] 200
# variables as parameters
a <- 4
print(foo5(x = a)) # x takes the value of a inside the function
## [1] 100
# CAN omit parameters that don't have defaults IF they are not used
foo6 <- function(x, y = 5) {
  return(y^2) # x not used!
}
print(foo6())
## [1] 25
# CAN also pass variables that are not defined IF they are not used
print(foo6(x = undef))
## [1] 25
# Explanation: R uses so-called lazy-evaluation, the parameters do NOT actually
# get evaluated for their value UNTIL they are needed (if they are never needed,
# it never checks whether they actually exist).
foo6b <- function(x, y = 5) {
  return(rlang::enquo(x)) # return the structure of the x argument
}
print(foo6b(x = undef)) # until its value is needed, x is just an expression defined in a specific environment
## <quosure>
##   expr: ^undef
##   env:  global
# special parameter ...
foo6c <- function(x, y = 5, ...) {
  str(list(...))
}
foo6c(x = 2, y = 5) # only named parameters provided
##  list()
foo6c(x = 2, z = 10, k = 9.81) # additional parameters provided in unspecific ...
## List of 2
##  $ z: num 10
##  $ k: num 9.81

6.3 Scoping

Variables defined inside functions (local variables) do NOT overwrite variables of the same name defined in the global environment. Local variables disappear once the function finishes.

# python
a = 2
def foo8():
  a = 395
  print(a) # print the value defined inside the function
print(a)
## 2
foo8()
## 395
print(a) # kept its original value
## 2
# R
a <- 2
foo8 <- function() {
  a <- 395
  print(a) # print the value defined inside the function
}
print(a)
## [1] 2
foo8()
## [1] 395
print(a) # kept its original value
## [1] 2

6.4 Scoping for objects

Also called passing by reference (as opposed to passing by value).

# python
# unlike primite data types (integers, numerics, strings, booleans), objects like 
# tuples, dicts, etc. get passed by reference and DO change inside the function
a_list = [3.14, 9.8]
def foo9(somelist):
  somelist[0] = 6.67e-11
  somelist.append(6.022e23)
  print(somelist) # print the value at end of function
print(a_list)
## [3.14, 9.8]
foo9(a_list)
## [6.67e-11, 9.8, 6.022e+23]
print(a_list) # did NOT keep its original value
# Advantage: large objects don't get copied each time
# Disadvantage: functions can alter global variables accidentally or without the
# user realizing
## [6.67e-11, 9.8, 6.022e+23]
# R
# even objects get passed by value (i.e. no passing by reference / do not get altered)
a_list <- list(3.14, 9.8)
foo9 <- function(somelist) {
  somelist[1] <- 6.67e-11
  somelist <- c(somelist, list(6.022e23))
  str(somelist)
}
str(a_list)
## List of 2
##  $ : num 3.14
##  $ : num 9.8
foo9(a_list)
## List of 3
##  $ : num 6.67e-11
##  $ : num 9.8
##  $ : num 6.02e+23
str(a_list) # a list stayed the same
## List of 2
##  $ : num 3.14
##  $ : num 9.8
# Advantage: functions don't accidentally change variables outside their scope
# Disadvantage: large objects get copied (although lazy evaluation helps some)
# R - changing global variables after all (NOTE: this is not recommended
# pratice in R)
a_list <- list(3.14, 9.8)
foo10 <- function() {
  a_list[1] <<- 6.67e-11 # using <<- assigns global variable
  a_list <- c(a_list, list(6.022e23)) # using <- does not
  str(a_list)
}
str(a_list)
## List of 2
##  $ : num 3.14
##  $ : num 9.8
foo10()
## List of 3
##  $ : num 6.67e-11
##  $ : num 9.8
##  $ : num 6.02e+23
str(a_list) # the command using <<- altered the global variable
## List of 2
##  $ : num 6.67e-11
##  $ : num 9.8
# BETTER: use return value and EXPLICITLY overwrite your global variable
a_list <- list(3.14, 9.8)
foo11 <- function(somelist) {
  somelist[1] <- 6.67e-11
  somelist <- c(somelist, list(6.022e23))
  return(somelist)
}
str(a_list)
## List of 2
##  $ : num 3.14
##  $ : num 9.8
str(foo11(a_list))
## List of 3
##  $ : num 6.67e-11
##  $ : num 9.8
##  $ : num 6.02e+23
str(a_list) # a list did not change
## List of 2
##  $ : num 3.14
##  $ : num 9.8
a_list <- foo11(a_list) # overwrite with new value returned by function
str(a_list) # now a_list has the new value BUT the function did not change it, the user did with the explicit re-assignment to a_list
## List of 3
##  $ : num 6.67e-11
##  $ : num 9.8
##  $ : num 6.02e+23