54
tot.price <- net.price * 1.12
}
round(tot.price)
}
If you send this code to the console, you can test the function. For example, if
you worked for 25 hours, the following code gives you the different amounts you
charge for public and private organizations, respectively:
> priceCalculator(25,public=TRUE)
[1] 1060
> priceCalculator(25,public=FALSE)
[1] 1120
This works well, but how does it work?
If you look at the
if...else
statement in the previous function, you find these
elements. If the value of the argument
public
is
TRUE
, the total price is calculated
as 1.06 times the net price. Otherwise, the total price is 1.12 times the net price.
The
if
statement needs a logical value between the parentheses. Any
expression you put between the parentheses is evaluated before it’s passed on
to the
if
statement. So, if you work with a logical value directly, you don’t
have to specify an expression at all. Using, for example,
if(public == TRUE)
is
about as redundant as asking if white snow is white. It would work, but it’s bad
coding practice.
Also, in the case of an
if...else
statement you can drop the braces if both
code blocks exist of only a single line of code. So, you could just forget about the
braces and squeeze the whole
if...else
statement on a single line. Or you could
even write it like this:
if(public) tot.price <- net.price * 1.06 else
tot.price <- net.price * 1.12
Putting the
else
statement at the end of a line and not the beginning of the
next one is a good idea. In general, R reads multiple lines as a single line as
long as it’s absolutely clear that the command isn’t finished yet (see Chapter
3). If you put
else
at the beginning of the second line, R considers the first line
finished and complains. You can put
else
at the beginning of a next line only if
48
you do so within a function and you source the complete file at once to R.
But you can still make this shorter. The
if
statement works like a function
and, hence, it also returns a value. As a result, you can assign that value to an
object or use it in calculations. So, instead of recalculating
net.price
and assigning
the result to
tot.price
within the code blocks, you can use the
if...else
statement like this:
tot.price <- net.price * if(public) 1.06 else 1.12
R will first evaluate the
if...else
statement, and multiply the outcome by
net.price
. The result of this is then assigned to
tot.price
. This differs not one iota
from the result of the five lines of code we used for the original
if...else
statement. R allows programmers to be incredibly lazy, er, economical here.
Vectorizing Choices
As we discuss in Chapter 4, vectorization is one of the defining attributes of
the R language. R wouldn’t be R if it didn’t have some kind of vectorized version of
an
if...else
statement. If you wonder why on earth you would need such a thing,
take a look at the problem discussed in this section.
Looking at the problem
The
priceCalculator()
function still isn’t very economical to use. If you have
100 clients, you’ll have to calculate the price for every client separately. Check for
yourself what happens if you add, for example, three different amounts of hours as
an argument:
> priceCalculator(c(25,110))
[1] 1060 4664
Warning message:
In if (hours > 100) net.price <- net.price * 0.9 :
the condition has length > 1 and only the first element will be used
Not only does R warn you that something fishy is going on, but the result you
get is plain wrong. Instead of $4,664, the second client should be charged only
$4,198:
> priceCalculator(110)
[1] 4198
40
What happened? The warning message should give you a fair idea about what
went on. An
if
statement can deal only with a single value, but the expression
hours > 100
returns two values, as shown by the following code:
> c(25, 110) > 100
[1] FALSE TRUE
Choosing based on a logical vector
The solution you’re looking for is the
ifelse()
function, which is a vectorized
way of choosing values from two vectors. This remarkable function takes three
arguments:
A test vector with logical values
A vector with values that should be returned if the corresponding value in the
test vector is
TRUE
A vector with values that should be returned if the corresponding value in the
test vector is
FALSE
Understanding how it works
Take a look at the following trivial example:
> ifelse(c(1,3) < 2.5 , 1:2 , 3:4)
[1] 1 4
To understand how it works, run over the steps the function takes:
1. The conditional expression
c(1,3) < 2.5
is evaluated to a logical
vector.
2. The first value of this vector is
TRUE
, because 1 is smaller than 2.5.
So, the first value of the result is the first value of the second argument,
which is 1.
3. The next value is
FALSE
, because 3 is larger than 2.5. Hence,
ifelse()
takes the second value of the third argument (which is 4) as the
47
second value of the result.
4. A vector with the selected values is returned as the result.
Trying it out
To see how this works in the example of the
priceCalculator()
function, try
the function out at the command line in the console. Say you have two clients and
you worked 25 and 110 hours for them, respectively. You can calculate the net
price with the following code:
> my.hours <- c(25,110)
> my.hours * 40 * ifelse(my.hours > 100, 0.9, 1)
[1] 1000 3960
Didn’t you just read that the second and third arguments should be a vector?
Yes, but the
ifelse()
function can recycle its arguments. And that’s exactly what it
does here. In the preceding
ifelse()
function call, you translate the logical vector
created by the expression
my.hours > 100
into a vector containing the numbers 0.9
and 1 in lieu of
TRUE
and
FALSE
, respectively.
Adapting the function
Of course, you need to adapt the
priceCalculator()
function in such a way
that you also can input a vector with values for the argument
public
. Otherwise,
you wouldn’t be able to calculate the prices for a mixture of public and private
clients. The final function looks like this:
priceCalculator <- function(hours,pph=40,public){
net.price <- hours * pph
net.price <- net.price * ifelse(hours > 100 , 0.9, 1)
tot.price <- net.price * ifelse(public, 1.06, 1.12)
round(price)
}
Next, create a little data frame to test the function. For example:
> clients <- data.frame(
+ hours = c(25, 110, 125, 40),
+ public = c(TRUE,TRUE,FALSE,FALSE)
+)
46
You can use this data frame now as arguments for the
priceCalculator()
function, like this:
> with(clients, priceCalculator(hours, public = public))
[1] 1060 4198 5040 1792
There you go. Problem solved!
Making Multiple Choices
The
if
and
if...else
statements you use in the previous section leave you
with exactly two options, but life is seldom as simple as that. Imagine you have
some clients abroad.
Let’s assume that any client abroad doesn’t need to pay VAT for the sake of
the example. This leaves you now with three different VAT rates: 12 percent for
private clients, 6 percent for public clients, and none for foreign clients.
Chaining if...else statements
The most intuitive way to solve this problem is just to chain the choices. If a
client is living abroad, don’t charge any VAT. Otherwise, check whether the client is
public or private and apply the relevant VAT rate.
If you define an argument
client
for your function that can take the values
‘abroad’
,
‘public’
, and
‘private’
, you could code the previous algorithm like this:
if(client==’private’){
tot.price <- net.price * 1.12 # 12% VAT
} else {
if(client==’public’){
tot.price <- net.price * 1.06 # 6% VAT
} else {
tot.price <- net.price * 1 # 0% VAT
}
}
With this code, you nest the second
if...else
statement in the first
if...else
statement. That’s perfectly acceptable and it will work, but imagine what you
would have to do if you had four or even more possibilities. Nesting a statement in
a statement in a statement in a statement quickly creates one huge curly mess.
47
Luckily, R allows you to write all that code a bit more clearly. You can chain
the
if...else
statements as follows:
if(client==’private’){
tot.price <- net.price * 1.12
} else if(client==’public’){
tot.price <- net.price * 1.06
} else {
tot.price <- net.price
}
In this example, the chaining makes a difference of only two braces, but when
you have more possibilities, it really makes the difference between readable code
and sleepless nights. Note, also, that you don’t have to test whether the argument
client
is equal to
‘abroad’
(although it wouldn’t be wrong to do that). You just
assume that if
client
doesn’t have any of the two other values, it has to be
‘abroad’
.
Chained
if...else
statements work on a single value at a time. You can’t
use these chained
if...else
statements in a vectorized way. For that, you can
nest multiple
ifelse
statements, like this:
VAT <- ifelse(client==’private’, 1.12,
ifelse(client == ‘public’, 1.06, 1)
)
tot.price <- net.price * VAT
This piece of code can become quite confusing if you have more than three
choices, though. The solution to this is to switch.
Switching between possibilities
The nested
if...else
statement is especially useful if you have complete code
blocks that have to be carried out when a condition is met. But if you need to
select values based only on a condition, there’s a better option: Use the
switch()
function.
Making choices with switch
52
In the previous example, you wanted to adjust the VAT rate depending on
whether the client is a public one, is a private one, or lives abroad. You have a list
of three possible choices, and for each choice you have a specific VAT rate. You can
use the
switch()
function like this:
VAT <- switch(client, private=1.12, public=1.06, abroad=1)
You construct a
switch()
call as follows:
1. Give a single value as the first argument (in this case, the value of
client
).
Note that
switch()
isn’t vectorized, so it can’t deal with vectors as a first
argument.
2. After the first argument, you give a list of choices with the
respected values.
Note that you don’t have to put quotation marks around the choices.
Remember that
switch()
doesn’t work in a vectorized way. You can
distinguish the choices more easily, however, so the code becomes more
readable.
In fact, the first argument doesn’t have to be a value; it can be some
expression that evaluates to either a character vector or a number. In case you
work with numbers, you don’t even have to use
choice=value
in the function
call. If you have integers,
switch()
will return the option in that position. In
the statement
switch(2,’some value’, ‘something else’, ‘some more’)
, the
result is
‘something else’
. You can find more information and examples on
the Help page
?switch
.
Using default values in switch
You don’t have to specify all options in a
switch()
call. If you want to have a
certain result in case the matched value is not among the specified options, put
that result as the last option, without any choice before it. So, the following line of
code does exactly the same thing as the nested
ifelse
call from “Chaining if...else
statements” section, earlier in this chapter:
43
VAT <- switch(client, private=1.12, public=1.06, 1)
You can easily test this out in the console by creating an object called
client
with a certain value and then running the
switch()
call, as in the
following example:
> client <- ‘other’
> switch(client, private=1.12, public=1.06, 1)
[1] 1
You can give
client
different values to see how
switch()
works.
Looping Through Values
In the previous section, you use a couple different methods to make choices.
Many of these methods aren’t vectorized, so you can use only a single value to
base your choice on. You could, of course, apply that code on each value you have
by hand, but it makes far more sense to automate this task.
Constructing a for loop
As in many other programming languages, you repeat an action for every
value in a vector by using a
for
loop. You construct a
for
loop in R as follows:
for(i in values){
... do something ...
}
This
for
loop consists of the following parts:
The keyword
for
, followed by parentheses.
An identifier between the parentheses. In this example, we use
i
, but that can
be any object name you like.
The keyword
in
, which follows the identifier.
43
A vector with values to loop over. In this example code, we use the object
values
, but that again can be any vector you have available.
A code block between braces that has to be carried out for every value in the
object
values
.
In the code block, you can use the identifier. Each time R loops through the
code, R assigns the next value in the vector with values to the identifier.
Calculating values in a for loop
Let’s take another look at the
priceCalculator()
function (refer to the
“Making Multiple Choices” section, earlier in this chapter). Earlier, we show you a
few possibilities to adapt this function so you can apply a different VAT rate for
public, private, and foreign clients. You can’t use any of these options in a
vectorized way, but you can use a
for
loop so the function can calculate the price
for multiple clients at once.
Using the values of the vector
Adapt the
priceCalculator()
function as follows:
priceCalculator <- function(hours, pph=40, client){
net.price <- hours * pph *
ifelse(hours > 100, 0.9, 1)
VAT <- numeric(0)
for(i in client){
VAT <- c(VAT,switch(i, private=1.12, public=1.06, 1))
}
tot.price <- net.price * VAT
round(tot.price)
}
The first and the last part of the function haven’t changed, but in the middle
section, you do the following:
1. Create a numeric vector with length 0 and call it
VAT
.
2. For every value in the vector client, apply
switch()
to select the
correct amount of VAT to be paid.
Documents you may be interested
Documents you may be interested