Dr. Mark Gardener

Home

Index of MonogRaphs

# R: MonogRaphs

### A series of essays on random topics using R: The Statistical Programming Language

R is a powerful and flexible beast. Getting started using R is not too difficult and you can learn to start using R in an afternoon. However, mastering R takes rather longer! These monographs are my way of exploring various topics in a completely unstructured manner.

The 3 Rs:

Writing Data
Doing math (Arithmetic)

Top

## Part 3. Arithmetic

I remember reading about the 3 Rs as the most important topics in education: reading writing and arithmetic. I thought it would be interesting to examine these in an R context e.g. reading data, writing data and doing maths. This page is all about arithmetic, part 1 was about Reading, part 2 was about Writing.

### Index for Part 3.

 Operators Logical Operators Complex numbers Powers (exponents) AND Rounding and precision Modulo and Integer division OR Scientific format Matrix operations NOT Extremes Matix multiplication Exclusive OR Logarithms Cross Products Comparisons Trigonometry Other matrix math Selection and Matching Summation Match any item Cumulative functions Match all items Compare objects Compare and return index

Use R like a calculator

## Introduction

You can think of R as like a giant calculator. It has a great capacity for mathematical operations. In this article I'll look at some of the more basic ones, to give a flavour of what basic arithmetic you can do.

Basic math operators:
() * / + -

Exponents use: ^

Note order of operations:
() ^ * / + -

Use sqrt() or x^0.5 for square root.

Modulo: %%
Integer division: %/%

Top

## Operators

In the main your arithmetic is going to involve adding up, dividing, subtracting and multiplication. These things are all carried out using basic operators that are familiar to everyone: + - * / and ().

The order of the operators is important. R will evaluate your maths in a set order. multiplication (*) and division (/) are evaluated first. Then addition (+) and subtraction (-). As well as this basic order, things inside parentheses () will be evaluated before anything "outside" the parentheses (still in the */+- order). So, remember this running order when you type your maths e.g.

```7 + 4 * 11[1] 51
(7 + 4) * 11[1] 121```

As well as the basic operators there are some "extras".

Powers (i.e. exponents) are designated using the caret (^) character. These are evaluated before any of the other operators. For example:

```2^2 ; 2^3 ; 2^4[1] 4[1] 8[1] 16
3 * 2^3 + 1[1] 25```

If you want a fractional power you enclose this in parentheses e.g.

```64^(1/2)[1] 8
64^(-2)[1] 0.0002441406```

A square root is simply a power of 0.5 e.g. x^0.5 but there is a special command sqrt() to deal with this.

`x = c(9, 16, 25, 36, 49, 59)sqrt(x)[1] 3.000000 4.000000 5.000000 6.000000 7.000000 7.681146`

You can use the % symbol to compute modulo (%%) and integer division (%/%) like so:

```16 %/% 3[1] 5
16 %% 3[1] 1```

So, you have 16 ÷ 3 giving 5 and 1 remainder.

Matrix multiplication via:
%*%

Matrix cross products:
crossprod()
tcrossprod()

equivalent to:

t(x) %*% y
x %*% t(y)

Top

### Matrix operations

There are a bunch of commands associated with matrix math.

#### Matrix multiplication

Multiply two matrices using the %*% operator.

```## Make some matrices
(x = cbind(1,1:3,c(2,0,1)))     [,1] [,2] [,3][1,]    1    1    2[2,]    1    2    0[3,]    1    3    1
(y = c(1, 3, 2))[1] 1 3 2
(y1 = matrix(1:3, nrow = 1))     [,1] [,2] [,3][1,]    1    2    3
(z = matrix(3:1, ncol = 1))     [,1][1,]    3[2,]    2[3,]    1
## Various multiplications> x %*% y     [,1][1,]    8[2,]    7[3,]   12
x %*% y1 # Order can be importantError in x %*% y1 : non-conformable arguments
y1 %*% x     [,1] [,2] [,3][1,]    6   14    5
x %*% z     [,1][1,]    7[2,]    7[3,]   10
z %*% x # Order is importantError in z %*% x : non-conformable arguments
y %*% y # Matrix multiplication of two vectors     [,1][1,]   14      ```

#### Matrix Cross Products

You can compute the cross product using the %*% operator and the t() command. Alternatively the crossprod() and tcrossprod() commands can do the job:

```(x = cbind(1,1:3,c(2,0,1)))     [,1] [,2] [,3][1,]    1    1    2[2,]    1    2    0[3,]    1    3    1
(y = c(1, 3, 2))[1] 1 3 2
(y1 = matrix(1:3, nrow = 1))     [,1] [,2] [,3][1,]    1    2    3
## Cross products> crossprod(x, y) # Same as t(x) %*% y     [,1][1,]    6[2,]   13[3,]    4
crossprod(x, y1) # Gives error as y1 wrong "shape"Error in crossprod(x, y1) : non-conformable arguments
tcrossprod(x, y1) # Same as x %*% t(y1)     [,1][1,]    9[2,]    5[3,]   10
```

Miscellaneous matrix math commands:

backsolve()
forwardsolve()
chol()
det()
determinant()
diag()
eigen()
outer()
qr()
solve()
svd()

Top

#### Other matrix maths

There are various other commands associated with matrix math. They are not really operators (except %o%) as such but I'll list them here:

 backsolve forwardsolve These commands solve a system of linear equations where the coefficient matrix is upper (“right”, “R”) or lower (“left”, “L”) triangular. chol This command computes the Choleski factorization of a real matrix. The matrix must be symmetric, positive definite. The result returned is the upper triangular factor of the Choleski decomposition, that is, the matrix R such that R'R = x. det determinant The determinant command calculates the modulus of the determinant (optionally as a logarithm) and the sign of the determinant. The det command calculates the determinant of a matrix (it is a wrapper for the determinant command). diag Matrix diagonals. This command has several uses; it can extract or replace the diagonal of a matrix. Alternatively, the command can construct a diagonal matrix. eigen Computes eigenvalues and eigenvectors for matrix objects; that is, carries out spectral decomposition. The result is a list containing \$values and \$vectors. outer %o% The outer command calculates the outer product of arrays and matrix objects. The %o% symbol is a convenience wrapper for outer(X, Y, FUN = "*"). qr This command computes the QR decomposition of a matrix. It provides an interface to the tech- niques used in the LINPACK routine DQRDC or the LAPACK routines DGEQP3 and (for complex matrices) ZGEQP3. The result holds a class attribute "qr". solve Solves a system of equations. This command solves the equation a %*% x = b for x, where b can be either a vector or a matrix. svd The svd command computes the singular value decomposition of a rectangular matrix. The result is a list containing \$d, the singular values of x. If nu > 0 and nv > 0, the result also contains \$u and \$v, the left singular and right singular vectors of x.

Logical operators:

AND & &&
OR | ||
NOT !
Exclusive OR xor()

Logical operators work with comparison operators to produce TRUE FALSE result.

Top

### Logical Operators

Some operators are used to provide a logical result (i.e. TRUE or FALSE).

#### Logical AND

The & operator is used for the logical AND in an elementwise manner. If you double up the operator && you perform the operation on only the first element.

#### Logical OR

The vertical bar | is used for the logical OR in an elementwise manner. If you double up the operator || you perform the operation on only the first element.

#### Logical NOT

The ! operator is used for the logical NOT.

#### Logical Exclusive OR

The command xor(x, y) acts as an exclusive OR operator.

The logical operators work in conjunction with other operators (comparisons) to produce results. So, look at the examples in the next section.

Comparisons:
==
!=
<
<=
>=
>

Often used with Logical Operators

Top

### Comparisons

Comparison operators allow you to compare elements. These are often used in conjunction with the logical operators.

 == Equal to. Note that the single = denotes assignment so == is used as a comparison operator. != Not equal to. < Less than. <= Less than or equal to. >= Greater than or equal to. > Greater than.

Here are some simple examples:

```## Make some vectors
yy = c(2, 3, 6, 5, 3)zz = c(4, 2, 4, 7, 4)
yy ; zz
[1] 2 3 6 5 3[1] 4 2 4 7 4
yy > 4 # Elements greater than 4[1] FALSE FALSE  TRUE  TRUE FALSE
zz != 4 # Elements not equal to 4[1] FALSE  TRUE FALSE  TRUE FALSE
zz == 4 # Elements that are equal to 4[1]  TRUE FALSE  TRUE FALSE  TRUE
yy > 3 & zz > 4 # Two conditions AND[1] FALSE FALSE FALSE  TRUE FALSE
yy > 3 && zz > 4 # Test first element only[1] FALSE
yy == 3 | zz == 4 # Two conditions OR[1]  TRUE  TRUE  TRUE FALSE  TRUE

xor(yy > 3, zz > 4) # Exclusive OR[1] FALSE FALSE  TRUE FALSE FALSE```

Use any() to match any element in an object

Use all() to match all elements in an object

Top

### Selection and Matching

The comparison and logical operators are used to obtain TRUE/FALSE results. However, they are not always the best choice for selection. There are times when you are looking for a single logical result but using regular operators either fails, or produces more than one. In these cases the selection commands are helpful. They aren't strictly mathematical operators but it is helpful to be aware of them. Additionally the isTRUE() command can "force" a single logical as a result.

#### Match any item in an object

Use the any() command to return a single TRUE result if any element meets the specified criteria.

#### Match all items in an object

Use the all() command to return a single TRUE result if all elements meet the specified criteria.

Commands for returning single logical result:

identical()
all.equal()
isTRUE()

Top

### Compare objects

The identical() command compares two items and returns a TRUE if they are exactly equal. The all.equal() command is similar but with a bit more tolerance.

```(yy = c(2, 3, 6, 5, 3)) # Make a vector[1] 2 3 6 5 3
any(yy > 5) # Are any elements > 5?[1] TRUE
all(yy < 5) # Are all elements < 5?[1] FALSE
all(yy >= 2) # Are all elements >= 2?[1] TRUE
## Some simple values x1 = 0.5 - 0.3 x2 = 0.3 - 0.1
## Are results the same? x1 == x2[1] FALSE
## Set tolerance to 0 to show actual differenceall.equal(x1, x2, tolerance = 0)[1] "Mean relative difference: 1.387779e-16"
## Use default toleranceall.equal(x1, x2)[1] TRUE
# Wrap command in isTRUE() for logicalisTRUE(all.equal(x1, x2))[1] TRUE
isTRUE(all.equal(x1, x2, tolerance = 0))[1] FALSE
## Character vectors pp = c("a", "f", "h", "q", "r") qq = c("d", "e", "x", "c", "s")rr = pp

# Test for equalityall.equal(pp, qq)[1] "5 string mismatches"
identical(x1, x2)[1] FALSE
identical(pp, qq)[1] FALSE
identical(pp, rr)[1] TRUE      ```

Note that you can use identical(TRUE, x) in lieu of isTRUE(x), where x is the condition to test.

Use which() to return an index of which elements match criteria.

Top

### Selection with non-logical result

You can use the which() command to obtain an "index" instead of a logical result. The command works in conjuction with the comparison and logical operators but returns a result that indicates which elements match your criteria.

```yy = c(2, 3, 6, 5, 3)yy[1] 2 3 6 5 3
which(yy > 3)[1] 3 4
which(yy == 3)[1] 2 5```

Complex numbers have an imaginary part and have class "complex"

Make and "test" complex items using:
complex()
as.complex()
is.complex()

Commands for complex math:
Arg()
Conj()
Im()
Mod()
Re()

Regular math operators also work on complex numbers.

Top

## Complex numbers

Complex numbers are those with "imaginary" parts. You can make complex numbers using the complex() and as.complex() commands, whilst the is.complex() command provides a quick logical test to see if an object has the class "complex". R has several commands that can deal with complex numbers.

 Arg Returns the argument of an imaginary number. Conj Displays the complex conjugate for a complex number. Im Shows the imaginary part of a complex number. Mod Shows the modulus of a complex number. Re Shows the real part of complex numbers.

Here are some simple examples:

```## Make some complex numbers
z0 = complex(real = 1:8, imaginary = 8:1)z1 = complex(real = 4, imaginary = 3)z2 = complex(real = 4, imaginary = 3, argument = 2)z3 = complex(real = 4, imaginary = 3, modulus = 4, argument = 2)
z0 ; z1 ; z2 ; z3[1] 1+8i 2+7i 3+6i 4+5i 5+4i 6+3i 7+2i 8+1i[1] 4+3i[1] -0.4161468+0.9092974i[1] -1.664587+3.63719i
## Get the real and imaginary parts of a complex object
Re(z0)[1] 1 2 3 4 5 6 7 8
Im(z0)[1] 8 7 6 5 4 3 2 1

## Get the Argument and Modulus
Arg(z1)[1] 0.6435011
Mod(z1)[1] 5
## Display the complex conjugate
Conj(z2)
[1] -0.4161468-0.9092974i

## Get the modulus and argument
Mod(z3)[1] 4
Arg(z3)[1] 2```

Besides these special commands, the regular math operators work on complex numbers:

```z0 + z1[1]  5+11i  6+10i  7+ 9i  8+ 8i  9+ 7i 10+ 6i 11+ 5i 12+ 4i
z0 * z1[1] -20+35i -13+34i  -6+33i   1+32i   8+31i  15+30i  22+29i  29+28i
z2 / z3[1] 0.25+0i```

Precision and rounding:

abs()
sign()
floor()
celiling()
trunc()
round()
signif()

Commands work on most numeric objects.

On logicals TRUE returns 1 and FALSE returns 0

Top

## Rounding

There are various commands that deal generally with precision and rounding.

 abs This command returns the absolute magnitude of a numeric value (that is, ignores the sign). If it is used on a logical object the command produces 1 or 0 for TRUE or FALSE, respectively. sign This command returns the sign of elements in a vector. If negative an item is assigned a value of –1; if positive, +1; and if zero, 0. floor This command rounds values down to the nearest integer value. ceiling This command rounds up a value to the nearest integer. trunc Creates integer values by truncating items at the decimal point. round Rounds numeric values to a specified number of decimal places. signif This command returns a value rounded to the specified number of significant figures.

These are all fairly obvious; here are some examples:

```## Make some values
yy = log(2^(2:6))yy[1] 1.386294 2.079442 2.772589 3.465736 4.158883
floor(yy)[1] 1 2 2 3 4
ceiling(yy)[1] 2 3 3 4 5
trunc(yy)[1] 1 2 2 3 4
round(yy, digits = 3)[1] 1.386 2.079 2.773 3.466 4.159
signif(yy, digits = 3)[1] 1.39 2.08 2.77 3.47 4.16
## Include negative numbers
xx = -3:3xx[1] -3 -2 -1  0  1  2  3
sign(xx)[1] -1 -1 -1  0  1  1  1
abs(xx)[1] 3 2 1 0 1 2 3```

These commands work on most numeric objects (e.g. data.frame, vector, matrix, table). If you have logical objects you'll return 1 for a TRUE and 0 for a FALSE.

Add an exponent to a number to create a scientific format number. Use e or E
e.g. 1e+2 or 1E-03

Use format() command to "force" printing in scientific format or not. Result is a text object.

Use options(scipen = n) to change default number handling.

Top

### Scientific Format

You can enter numbers using an exponent to make it easier to deal with very large or very small values. The exponent is indicated by the letter e or E. You can use the - sign to indicate a negative exponent. The + sign can be omitted. You must not leave a space between the value and the exponent. You can only add an exponent to a numeric value and not to a named object.

```1e3[1] 1000
1E4[1] 10000
1 e-2Error: unexpected symbol in "1 e"1e-2[1] 0.01```

Values are generally "printed" by R in regular format but sometimes they will appear in scientific format. This makes no difference to your calculations but sometimes you want the result to be displayed in scientific format and at other times not. There are two ways to achive the result you want.

The simplest way to present your results objects in an appropriate format is to use the format() command. You simply set scientific = TRUE to prepare an object in that format (set FALSE to use regular format). The downside to this is that the object is prepared as a text result, which might be inconvenient.

The other way is to alter the options() and to set the scipen option. The default is 0. Negative values tend to produce scientific notation and positive values are less likely to do so.

```## Make some values
yy = c(1, 10, 100, 1000, 10000, 100000)zz = c(1, 12, 123, 1234, 12345, 123456)
yy ; zz[1] 1e+00 1e+01 1e+02 1e+03 1e+04 1e+05[1]      1     12    123   1234  12345 123456
## Use format() to force scientific or foxed format
format(yy, scientific = FALSE)[1] "     1" "    10" "   100" "  1000" " 10000" "100000"
format(zz, scientific = TRUE, digits = 3)[1] "1.00e+00" "1.20e+01" "1.23e+02" "1.23e+03" "1.23e+04" "1.23e+05"
## Check the scipen option
options("scipen")\$scipen[1] 0

## Set scipen
options(scipen = 1)> yy[1]      1     10    100   1000  10000 100000
options(scipen = -6)zz[1] 1.00000e+00 1.20000e+01 1.23000e+02 1.23400e+03 1.23450e+04 1.23456e+05
print(zz, digits = 3)[1] 1.00e+00 1.20e+01 1.23e+02 1.23e+03 1.23e+04 1.23e+05
options(scipen = 0) # Reset scipen```

You may need to tweak the values of scipen but in general the number of digits in the result is your guideline.

Extremes:
max()
min()
range()

Use order() to make index if you want nth largest or nth smallest values.

Top

## Extremes

The largest and smallest items can be extracted using max() and min() commands repsectively. These commands produce a single value as the result.

The range() command produces two values, the smallest and largest, in that order.

```## Set random number generator
set.seed(99)
## Make some values
xx = runif(n = 10, max = 100, min = -100)
max(xx)[1] 98.50176
min(xx)[1] -77.24366
range(xx)[1] -77.24366  98.50176
range(xx)[1] # Just the min value[1] -77.24366
range(xx)[2:1] # Display max then min[1]  98.50176 -77.24366```

If you want the 2nd largest, or the 3rd smallest (for example) then you need to use the order() command to get an "index". Set the sort order to decreasing = FALSE (the default) to get the smallest values, set decreasing = TRUE to get the largest values.

```xx[1]  16.942370 -77.243665  36.852949  98.501755   6.998717  93.322813  34.285512[8] -41.084458 -28.327403 -64.937049
order(xx) # Index in ascending order[1]  2 10  8  9  5  1  7  3  6  4
xx[order(xx)[1]] # 1st smallest (min)[1] -77.24366
xx[order(xx)[2]] # 2nd smallest[1] -64.93705

xx[order(xx, decreasing = TRUE)[1]] # 1st largest (max)[1] 98.50176
xx[order(xx, decreasing = TRUE)[2]] # 2nd largest[1] 93.32281
```

Logarithms:
log()
log10()
log2()
log1p()
exp()
expm1()

Default base is natural.

Top

## Logarithms

Logarithms and their reverse (anti-logs?) are dealt with using the log() and exp() commands repsectively. The default base is the natural log (e) but you can specify the base explicitly. There are also several convenience commands:

 log(x, base = exp(1)) The basic log command. The default base is natrual. Use base parameter to specify alternative (as long as it works out as a numeric). log10 A convenience function computes log base 10. log2 Computes log base 2. log1p Computes log(x + 1). See also expm1(). exp The antilog for base e. expm1 The antilog for base e -1 i.e. exp(x) -1. See also log1p().

Using the commands is fairly simple.

```log(1:4) # Natural log[1] 0.0000000 0.6931472 1.0986123 1.3862944
log(1:4, base = 3) # Log base 3[1] 0.0000000 0.6309298 1.0000000 1.2618595
log10(1:4) # Log base 10[1] 0.0000000 0.3010300 0.4771213 0.6020600
log2(1:4) # Log base 2[1] 0.000000 1.000000 1.584963 2.000000
log(0:3) # Regular log gives infinity for 0[1]      -Inf 0.0000000 0.6931472 1.0986123
log1p(0:3) # Add 1 to values then log[1] 0.0000000 0.6931472 1.0986123 1.3862944
10^0.6 # The antilog (base 10) of 0.6[1] 3.981072
3^0.63 # The antilog (base 3) of 0.63[1] 1.997958
exp(1.098) # The natural antilog of 1.098[1] 2.998164
expm1(1.098) # The natural antilog of 1.098 then minus 1[1] 1.998164```

Trigonometry:

cos() acos()
sin() asin()
tan() atan()

Hyperbolic functions:

cosh() acosh()
sinh() asinh()
tanh() atanh()

Top

## Trigonometry

R has a suite of commands that carry out trigonometric functions.

 Regular Hyperbolic Sine sin() asin() sinh() asinh() Cosine cos() acos() cosh() acosh() Tangent tan() atan() tanh() atanh()

The commands work out the basic functions and also hyperbolic equivalents. Angles are in radians (a right angle is pi/2 radians).

Here are some simple examples using the cosine:

```cos(45) # Angle in radians[1] 0.525322
cos(45 * pi/180) # Convert 45 radians to degrees[1] 0.7071068
acos(1/2) * 180/pi # To get result in degrees[1] 60
acos(sqrt(3)/2) * 180/pi # To get result in degrees[1] 30
cosh(0.5) # Hyperbolic function[1] 1.127626```

Summation

Add items using sum()

Get product of items using product()

Logical TRUE = 1
Logical FALSE = 0

prod(x:y) = factorial(y)

Related to gamma() function:

prod(x:y)
= gamma(y+1)
= factorial(y)

Top

## Summation

There are various commands associated with lists of values (that is list in a general sense, not an R list object).

The sum() command returns the sum of all the numbers specified. This works for most R objects, including data.frame, matrix and vectors. Logical values are treated as 1 (TRUE) or 0 (FALSE). NA items are treated as 0. You can "ignore" NA items using na.rm = TRUE as a parameter in the command.

#### Multiplying things

The prod() command returnd the product of all the numbers specified, that is each value multiplied by the "next". This works for most R objects, including data.frame, matrix and vectors (items are taken columnwise for data.frame objects). Logical values are treated as 1 (TRUE) or 0 (FALSE). NA items are treated as 0. You can "ignore" NA items using na.rm = TRUE as a parameter in the command.

```v = 1:9m = matrix(1:9, ncol = 3)d = data.frame(a = 1:3, b = 4:6, c = 7:9)v ; m ; d[1] 1 2 3 4 5 6 7 8 9
[,1] [,2] [,3][1,]    1    4    7[2,]    2    5    8[3,]    3    6    9
a b c1 1 4 72 2 5 83 3 6 9
## sum for vector, matrix and data.frame
sum(v)[1] 45
sum(m)[1] 45
sum(d) # Reads data columnwise[1] 45
## product for vector, matrix and data.frame
prod(v)[1] 362880
prod(m)[1] 362880
prod(d) # Reads data columnwise[1] 362880
## Make a locical vectorz = c(TRUE, TRUE, FALSE, TRUE)z[1]  TRUE  TRUE FALSE  TRUE
## Results for logicals
sum(z)[1] 3
prod(z)[1] 0```

The factorial() command is similar to prod(). With prod() you specify prod(x:y) as a sequence, whilst in factorial() you specify factorial(y). If you provide more than one value, you end up with multiple results:

```factorial(3) # i.e. 3 * 2 * 1[1] 6
factorial(5) # i.e. 5 * 4 * 3 * 2 * 1[1] 120
factorial(v)[1]      1      2      6     24    120    720   5040  40320 362880
factorial(m)     [,1] [,2]   [,3][1,]    1   24   5040[2,]    2  120  40320[3,]    6  720 362880```

You can also use the gamma() command, which equates to factorial(x-1). Essentially prod(x:y) = gamma(y+1) = factorial(y).

Cumulative functions:
cumsum()
cumprod()
cummax()
cummin()

Operate on vector and matrix objects directly.

Top

### Cumulative functions

There are several cumulative functions built-in to R. These determine sum, product, maximum and minimum.

 cumsum Determines the cumulative sum cumprod Cumulative product cummax Cumulative maximum cummin Cumulative minimum

The commands operate on numeric objects, usually vector or matrix objects. The commands work on data.frame objects but compute results per column. The commands do not work directly on lists (but you can use the lapply() command).

```v = 1:9m = matrix(1:9, ncol = 3)d = data.frame(a = 1:3, b = 4:6, c = 7:9)v ; m ; d[1] 1 2 3 4 5 6 7 8 9
[,1] [,2] [,3][1,]    1    4    7[2,]    2    5    8[3,]    3    6    9
a b c1 1 4 72 2 5 83 3 6 9
cumsum(v)[1]  1  3  6 10 15 21 28 36 45
cumprod(m)[1]      1      2      6     24    120    720   5040  40320 362880
cummax(d) # For data.frame calculations are carried out column by column  a b c1 1 4 72 2 5 83 3 6 9      ```

It is possible to make custom functions that calculate other cumulative results, but that is another story.

Top

There are many other mathematical functions in R. This has been a brief overview of some of the more "simple" arithmetic (although the foray into logic may not count as math).
Part 2. Writing | MongRaphs Index

See my Publications about statistics and data analysis.

Writer's Bloc – my latest writing project includes R scripts

Courses in data analysis, data management and statistics.

My Publications about statistics and data analysis

See my personal pages at GardenersOwn