Modifying big data is dangerous!

Finding example data

There is already a lot of data in the R library. The data() command displays all available data sets.
We are going to use the airquality data here:

# data()
str(airquality)
## 'data.frame':    153 obs. of  6 variables:
##  $ Ozone  : int  41 36 12 18 NA 28 23 19 8 NA ...
##  $ Solar.R: int  190 118 149 313 NA NA 299 99 19 194 ...
##  $ Wind   : num  7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
##  $ Temp   : int  67 72 74 62 56 66 65 59 61 69 ...
##  $ Month  : int  5 5 5 5 5 5 5 5 5 5 ...
##  $ Day    : int  1 2 3 4 5 6 7 8 9 10 ...
dim(airquality)
## [1] 153   6
head(airquality)
##   Ozone Solar.R Wind Temp Month Day
## 1    41     190  7.4   67     5   1
## 2    36     118  8.0   72     5   2
## 3    12     149 12.6   74     5   3
## 4    18     313 11.5   62     5   4
## 5    NA      NA 14.3   56     5   5
## 6    28      NA 14.9   66     5   6

The subset command

The subset function returns subsets of vectors, matrices or data frames which meet specified conditions. The following examples are from the subset function description (slightly modified).

In the following example, we extract data rows with temperature above 70 degrees, and keep only two columns (Ozone and Temp) of the original data frame:

sub1 = subset(airquality, Temp > 70, select = c(Ozone, Temp))
head(sub1)
##    Ozone Temp
## 2     36   72
## 3     12   74
## 11     7   74
## 22    11   73
## 29    45   81
## 30   115   79

Next, we keep only day1 rows, and skip the Temp column:

sub2 = subset(airquality, Day == 1, select = -Temp)
head(sub2)
##     Ozone Solar.R Wind Month Day
## 1      41     190  7.4     5   1
## 32     NA     286  8.6     6   1
## 62    135     269  4.1     7   1
## 93     39      83  6.9     8   1
## 124    96     167  6.9     9   1

We select all columns from Ozone to Wind (using the colon):

sub3 = subset(airquality, select = Ozone:Wind)
head(sub3)
##   Ozone Solar.R Wind
## 1    41     190  7.4
## 2    36     118  8.0
## 3    12     149 12.6
## 4    18     313 11.5
## 5    NA      NA 14.3
## 6    28      NA 14.9

The logical drop parameter tells the function if the original data structure should be preserved or not.
The default value is FALSE. We give an example where only one column is selected:

sub4 = subset(airquality, Day == 1, select = Temp, drop = FALSE)  # drop = FALSE must not be stated explicitely 
str(sub4)
## 'data.frame':    5 obs. of  1 variable:
##  $ Temp: int  67 78 84 81 91

We see that sub4 is still a data frame, even though it consists of one vector only (Temp).

Now, different from above, we declare drop = TRUE:

sub5 = subset(airquality, Day == 1, select = Temp, drop = TRUE)  # drop = TRUE ! 
str(sub5)
##  int [1:5] 67 78 84 81 91

As a result, the command keeps only a vector. The data frame structure has been dropped.

The merge command

The merge command can join two data frames by common columns or row names.

Example data

Let us create some example data sets:

df1 <- data.frame(  firstname = c("hans", "peter", "mary", "steve", "katie"),  
                      surname = c("lund", "mueller", "kaminski", "smith", "brendel"),
                      profession = c("dentist", "trucker", "midwife", "dentist", "physician"))
df1
##   firstname  surname profession
## 1      hans     lund    dentist
## 2     peter  mueller    trucker
## 3      mary kaminski    midwife
## 4     steve    smith    dentist
## 5     katie  brendel  physician

We create a slightly different data frame df2 by changing the column name surname to name:

df2 = df1
colnames(df2)[2] = "name"
# alternative :  df2 <- within(df1, { name <- surname; rm(surname) })
df2
##   firstname     name profession
## 1      hans     lund    dentist
## 2     peter  mueller    trucker
## 3      mary kaminski    midwife
## 4     steve    smith    dentist
## 5     katie  brendel  physician

A third data frame:

cars <- data.frame(
    name = c("lund", "mueller", "kaminski", "smith", "brendel", "weller", "cooper"),
    type = c("kia", "vw", "ford", "bmw", "bmw", "fiat", "audi"),
    color = c("pink", "red", "green", "blue", "white", "black", "white"),
    miles = c(1200, 400, 650, 1590, 8420, 5000, 7200))
cars
##       name type color miles
## 1     lund  kia  pink  1200
## 2  mueller   vw   red   400
## 3 kaminski ford green   650
## 4    smith  bmw  blue  1590
## 5  brendel  bmw white  8420
## 6   weller fiat black  5000
## 7   cooper audi white  7200

Merging on columns with identical names

By default, the data frames are merged on columns with identical names. If we merge df2 and cars, the common column name is name. We have 5 common entries of both frames in the name column:

intersect(df2$name, cars$name)
## [1] "lund"     "mueller"  "kaminski" "smith"    "brendel"

Consequently, we should see 5 rows in the merged data set:

merge1 <- merge(df2, cars)
merge1
##       name firstname profession type color miles
## 1  brendel     katie  physician  bmw white  8420
## 2 kaminski      mary    midwife ford green   650
## 3     lund      hans    dentist  kia  pink  1200
## 4  mueller     peter    trucker   vw   red   400
## 5    smith     steve    dentist  bmw  blue  1590

Specifying the column for joining explicitely is better

However, I suggest to specify the column for joining explicitely, because it feels safer. The parameter by declares the column to be used for joining:

merge(df2, cars, by = "name")
##       name firstname profession type color miles
## 1  brendel     katie  physician  bmw white  8420
## 2 kaminski      mary    midwife ford green   650
## 3     lund      hans    dentist  kia  pink  1200
## 4  mueller     peter    trucker   vw   red   400
## 5    smith     steve    dentist  bmw  blue  1590

Of course, this requires that there is a column with that name in both data frames.

Columns with the same names but different entries

In the cases above, the profession columns of the merged data frames had identical entries, so that we need only one profession column in the output. What if we have profession columns in the datasets which are not identical?

df3 = df2
df3$profession[3] = "director"
df3$profession
## [1] "dentist"   "trucker"   "director"  "dentist"   "physician"

Let us have a look at df1 and df3 once more:

df1
##   firstname  surname profession
## 1      hans     lund    dentist
## 2     peter  mueller    trucker
## 3      mary kaminski    midwife
## 4     steve    smith    dentist
## 5     katie  brendel  physician
df3
##   firstname     name profession
## 1      hans     lund    dentist
## 2     peter  mueller    trucker
## 3      mary kaminski   director
## 4     steve    smith    dentist
## 5     katie  brendel  physician
merge(df1, df3, by = "firstname")
##   firstname  surname profession.x     name profession.y
## 1      hans     lund      dentist     lund      dentist
## 2     katie  brendel    physician  brendel    physician
## 3      mary kaminski      midwife kaminski     director
## 4     peter  mueller      trucker  mueller      trucker
## 5     steve    smith      dentist    smith      dentist

Now, because the original profession columns are not identical, merge has created two profession columns with the suffixes .x and .y, respectively.

Joining on columns with different names

What if we want to join on columns with different names in the two data sets? For instance, df1 and cars have no common column names:

intersect(colnames(df1), colnames(cars))
## character(0)

…. but the entries in name and surname are overlapping, so we could join based on them:

df1$surname
## [1] "lund"     "mueller"  "kaminski" "smith"    "brendel"
cars$name
## [1] "lund"     "mueller"  "kaminski" "smith"    "brendel"  "weller"   "cooper"

Using the parameters by.x and by.y, we can specify which columns to use for merging in the corresponding datasets:

merge(cars, df1, by.x = "name", by.y = "surname")
##       name type color miles firstname profession
## 1  brendel  bmw white  8420     katie  physician
## 2 kaminski ford green   650      mary    midwife
## 3     lund  kia  pink  1200      hans    dentist
## 4  mueller   vw   red   400     peter    trucker
## 5    smith  bmw  blue  1590     steve    dentist

Keeping all entries when joining, instead of common ones only

When using surname in df1 and name in cars for joining, we have only 5 entries in the output, the common entries for these columns.

If we want all names/surnames in the merged dataset, we can specify all = TRUE:

m2 = merge(cars, df1, by.x = "name", by.y = "surname", all = TRUE)
m2
##       name type color miles firstname profession
## 1  brendel  bmw white  8420     katie  physician
## 2   cooper audi white  7200      <NA>       <NA>
## 3 kaminski ford green   650      mary    midwife
## 4     lund  kia  pink  1200      hans    dentist
## 5  mueller   vw   red   400     peter    trucker
## 6    smith  bmw  blue  1590     steve    dentist
## 7   weller fiat black  5000      <NA>       <NA>

If we do so, we have to tolerate unassigned fields (NA) in the output, because there is no cooper and weller in the surname column of df1.

Skipping rows with unassigned fields

We have seen that the frame m2 contains unassigned fields, marked by NA. If we have to remove rows with such fields, we can use the complete.cases command:

complete.cases(m2)
## [1]  TRUE FALSE  TRUE  TRUE  TRUE  TRUE FALSE

The result of complete.cases() is a logical vector indicating rows with complete data (TRUE), and rows containing NA (FALSE). We can utilize this function to clean a data frame from rows with unassigned fields:

m3 = m2[complete.cases(m2), ]
m3
##       name type color miles firstname profession
## 1  brendel  bmw white  8420     katie  physician
## 3 kaminski ford green   650      mary    midwife
## 4     lund  kia  pink  1200      hans    dentist
## 5  mueller   vw   red   400     peter    trucker
## 6    smith  bmw  blue  1590     steve    dentist

We have used the []-operator to select rows with TRUE-values.

Removing columns or rows from data frames

dfa = data.frame(eins = c("a", "b", "c", "d"), zwei = 6:9, drei = 3:6, vier = c("s", "t", "u", "v")) 
dfb = data.frame(eins = c("c", "a", "d", "b"), zwei = 5:8, drei = 2:5, vier = 3:6)
rownames(dfa) = c("one", "two", "three", "four")
rownames(dfb) = rownames(dfa)
dfa
##       eins zwei drei vier
## one      a    6    3    s
## two      b    7    4    t
## three    c    8    5    u
## four     d    9    6    v
dfb
##       eins zwei drei vier
## one      c    5    2    3
## two      a    6    3    4
## three    d    7    4    5
## four     b    8    5    6

Some examples:

dfa = dfa[-1,]     # remove first row
dfa
##       eins zwei drei vier
## two      b    7    4    t
## three    c    8    5    u
## four     d    9    6    v
dfa$eins <- NULL   # remove column named "eins"
dfa
##       zwei drei vier
## two      7    4    t
## three    8    5    u
## four     9    6    v
dfb[,2:3] <- NULL  # remove two columns by index 
dfb
##       eins vier
## one      c    3
## two      a    4
## three    d    5
## four     b    6

Sorting of data frames

The match command

In some situations, it might be necessary to reorder the rows or columns of a data frame or matrix. This can be accomplished by using the match function. Let us create some example data:

dfa = data.frame(eins = c("a", "b", "c", "d"), zwei = 6:9, drei = 3:6, vier = c("s", "t", "u", "v")) 
rownames(dfa) = c("one", "two", "three", "four")
dfa
##       eins zwei drei vier
## one      a    6    3    s
## two      b    7    4    t
## three    c    8    5    u
## four     d    9    6    v


Match to a target vector

We store the desired order in a target vector with the goal to reorder dfa so that dfa$eins is in this order. The match command finds the positions of target in the dfa$eins column:

target <- c("b", "c", "a", "d")  # desired order of the rows
order = match(target, dfa$eins) 
order 
## [1] 2 3 1 4

The match function takes two vectors as arguments. It yields the positions of the first vector in the second vector (here saved in the integer vector order). We see that b comes second in dfa$eins, c comes third in dfa$eins, a comes first in dfa$eins, and d comes forth in dfa$eins.
Using this information, we can now rearrange the rows of the frame dfa:

dfa_ordered = dfa[order,]
dfa_ordered
##       eins zwei drei vier
## two      b    7    4    t
## three    c    8    5    u
## one      a    6    3    s
## four     d    9    6    v

As it happens, this is the desired order of the data frame stored in target above.

We can of course shorten the procedure:

target <- c("b", "c", "a", "d")  # desired order of the rows
dfa_ordered = dfa[match(target, dfa$eins),]
dfa_ordered
##       eins zwei drei vier
## two      b    7    4    t
## three    c    8    5    u
## one      a    6    3    s
## four     d    9    6    v

Summary: The code above rearranges dfa$eins according to target.


Use columns of data frames for alignation

A second frame:

dfb = data.frame(zwei = c("c", "a", "d", "b"), vier = 5:8, eins = 2:5, drei = 3:6)
rownames(dfb) = c("two", "four", "three", "one")
dfb
##       zwei vier eins drei
## two      c    5    2    3
## four     a    6    3    4
## three    d    7    4    5
## one      b    8    5    6

Now, we order dfb$zwei according to dfa$eins, i.e. dfa$eins is the target vector.

intersect(dfb$zwei, dfa$eins) #  must have the same entries !
## [1] "c" "a" "d" "b"
dfa$eins
## [1] "a" "b" "c" "d"
target = dfa$eins 
order = match(target, dfb$zwei) 
dfb_neu = dfb[order,]
dfb_neu
##       zwei vier eins drei
## four     a    6    3    4
## one      b    8    5    6
## two      c    5    2    3
## three    d    7    4    5

If we want to have this very concise, we can write:

dfb_neu = dfb[match(dfa$eins, dfb$zwei),]
dfb_neu
##       zwei vier eins drei
## four     a    6    3    4
## one      b    8    5    6
## two      c    5    2    3
## three    d    7    4    5

Summary: The above command rearranges dfb$zwei according to dfa$eins.


Align row names of two arrays

We can also carry out this operation based on the row names, if there are any.

rownames(dfa) 
## [1] "one"   "two"   "three" "four"
rownames(dfb) 
## [1] "two"   "four"  "three" "one"

We reorder the rows of dfb so that dfb has the same order of row names as dfa:

match(rownames(dfa), rownames(dfb))           # rownames(dfa) is the target vector !
## [1] 4 1 3 2
dfb[match(rownames(dfa), rownames(dfb)), ]
##       zwei vier eins drei
## one      b    8    5    6
## two      c    5    2    3
## three    d    7    4    5
## four     a    6    3    4

Summary: The above command reorders dfb so that it has the same order of row names as dfa.


Align column names of two arrays

In a similar way, we can also adapt the order of the columns. Let’s have a look at an example:

colnames(dfa) 
## [1] "eins" "zwei" "drei" "vier"
colnames(dfb)
## [1] "zwei" "vier" "eins" "drei"

Our goul is to reorder the columns of dfb, so that their column names agree with those of dfa.

match(colnames(dfa), colnames(dfb))         # colnames(dfa) is the target
## [1] 3 1 4 2

Note, that the match command comes after the comma because we reorder columns:

dfb[, match(colnames(dfa), colnames(dfb))]  # match command after comma
##       eins zwei drei vier
## two      2    c    3    5
## four     3    a    4    6
## three    4    d    5    7
## one      5    b    6    8

Summary: The above command reorders the column names of dfb so that they have the same order as in dfa.

Searching in data

The grep command (or one of its flavours) can be used to search words in character vectors.

v0 = c("A", "B", "C", "D", "A", "a", "B", "B", "D", "E", "E","X", "XX", "DDD", "A", "XX", "FFFF", "0000", "12", "000")
grep("B", v0)
## [1] 2 7 8

We see that the command outputs the indices of the matches. Using this information, we can create a new vector containing only the matches:

v1 = v0[grep("B", v0)]
v1
## [1] "B" "B" "B"

Apply functions to every row or column

The above grep command works only for vectors. If we want to work on every row or column of a frame or matrix, we can use the apply command.

Some example data:

v0 = c("A", "B", "C", "D", "W", "B")
v1 = c("X", "B", "C", "BB", "A", "B")
v2 = c("A", "B", "C", "D", "W", "B")
v3 = c("X", "T", "X", "CC", "S", "U")
Vall = data.frame(one = v0, two = v1, three = v2, four = v3)
rownames(Vall) = c("eins", "zwei", "drei", "vier", "fuenf", "sechs")
Vall
##       one two three four
## eins    A   X     A    X
## zwei    B   B     B    T
## drei    C   C     C    X
## vier    D  BB     D   CC
## fuenf   W   A     W    S
## sechs   B   B     B    U
grep("B", Vall)
## [1] 1 2 3

The output of grep shows only which columns contain the “B”. If we want the positions in each column of the frame, we can use the apply command:

apply(Vall, 2, grep, pattern = "B") # "pattern" is a formal parameter of the grep function 
## $one
## [1] 2 6
## 
## $two
## [1] 2 4 6
## 
## $three
## [1] 2 6
## 
## $four
## integer(0)

We can also search by row. Therefore, we invoke a 1 as second parameter of the command:

apply(Vall, 1, grep, pattern = "B") # "pattern" is a formal parameter of the grep function!
## $eins
## integer(0)
## 
## $zwei
## [1] 1 2 3
## 
## $drei
## integer(0)
## 
## $vier
## [1] 2
## 
## $fuenf
## integer(0)
## 
## $sechs
## [1] 1 2 3

We can also run commands on both rows and columns. Moreover, we can use own functions to tell what to do with the entries:

a = matrix(seq(1,16), nrow=4)
a
##      [,1] [,2] [,3] [,4]
## [1,]    1    5    9   13
## [2,]    2    6   10   14
## [3,]    3    7   11   15
## [4,]    4    8   12   16
apply(a, c(1,2), function(x) {x^2})  # square of all elements!
##      [,1] [,2] [,3] [,4]
## [1,]    1   25   81  169
## [2,]    4   36  100  196
## [3,]    9   49  121  225
## [4,]   16   64  144  256

The function definition can also be outside the apply command:

expo <- function(x, lambda = 1) {exp(-lambda*x)}  # function definition 
ex = apply(a, c(1,2), expo, lambda = 2)           # "lambda" is a formal parameter of "expo"
ex
##              [,1]         [,2]         [,3]         [,4]
## [1,] 0.1353352832 4.539993e-05 1.522998e-08 5.109089e-12
## [2,] 0.0183156389 6.144212e-06 2.061154e-09 6.914400e-13
## [3,] 0.0024787522 8.315287e-07 2.789468e-10 9.357623e-14
## [4,] 0.0003354626 1.125352e-07 3.775135e-11 1.266417e-14

There are more flavours of the apply function, all listed here.

A few words about regular expressions

Did you notice that the command also considered “BB” as a match? (See the row named vier.)

If we only want matches with a single “B”, we have to modify the search pattern. We enclose the B with the begin (^) and end ($) token:

apply(Vall, 1, grep, pattern = "^B$")
## $eins
## integer(0)
## 
## $zwei
## [1] 1 2 3
## 
## $drei
## integer(0)
## 
## $vier
## integer(0)
## 
## $fuenf
## integer(0)
## 
## $sechs
## [1] 1 2 3

Now, there isn’t any match in the row named vier.

Construction of such search patterns, called regular expressions, can be quite complicated. Explanation of regular expressions is out of the scope of this document. However, there are lots of manuals and cheatsheets on the web, for instance here or here.

Replacing entries

To replace entries in data, we can use the sub or gsub command. We show a few examples:

 v = c("a", "b", "aa", "d")
 sub("a", "x", v)
## [1] "x"  "b"  "xa" "d"

This converts “a” to “x”, and “aa” to “xa” in the vector v. The second is possibly not what we want. To also change “aa” to “xx”, we can use gsub:

v = c("a", "b", "aa", "d")
gsub("a", "x", v)
## [1] "x"  "b"  "xx" "d"

If we want to make changes in arrays, we can again facilitate the apply command:

dfc = data.frame(eins = c("a", "b", "aa", "d"), zwei = 6:9, drei = 3:6, vier = c("a", "aaa", "u", "v")) 
dfc
##   eins zwei drei vier
## 1    a    6    3    a
## 2    b    7    4  aaa
## 3   aa    8    5    u
## 4    d    9    6    v
apply(dfc, 1, sub, pattern = "a", replacement = "x")  # "pattern" and "replacement" are formal parameters of "sub"
##      [,1] [,2]  [,3] [,4]
## eins "x"  "b"   "xa" "d" 
## zwei "6"  "7"   "8"  "9" 
## drei "3"  "4"   "5"  "6" 
## vier "x"  "xaa" "u"  "v"

This converts “aa” to “xa”. For some reason, the output matrix is transposed. We can reverse this with the t() function:

dfc_neu = apply(dfc, 1, sub, pattern = "a", replacement = "x")  # pattern and replacement are formal parameters of sub
dfc_neu = t(dfc_neu)  # transpose
dfc_neu
##      eins zwei drei vier 
## [1,] "x"  "6"  "3"  "x"  
## [2,] "b"  "7"  "4"  "xaa"
## [3,] "xa" "8"  "5"  "u"  
## [4,] "d"  "9"  "6"  "v"

Again, we use gsub to completely convert all “a” characters:

t(apply(dfc, 1, gsub, pattern = "a", replacement = "x"))  # # pattern and replacement are formal parameters of sub
##      eins zwei drei vier 
## [1,] "x"  "6"  "3"  "x"  
## [2,] "b"  "7"  "4"  "xxx"
## [3,] "xx" "8"  "5"  "u"  
## [4,] "d"  "9"  "6"  "v"

Here, we took care of transposing the output at the same time.

Finding duplicates and making data unique

To identify duplicates in data, the duplicated command is useful. Let’s have a look at an example:

v1 = c("a", "b", "c", "a", "f", "c", "c", "f", "g")
duplicated(v1)
## [1] FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE  TRUE FALSE

The output is a logical vector with TRUE values at redundant locations. We can use this information to create a vector without the duplicated items:

v1_cleaned = v1[!duplicated(v1)]   # "!" stands for logical NOT !
v1_cleaned
## [1] "a" "b" "c" "f" "g"

Do not overlook the logical NOT brought about by the !-operator! in the code above.

We can also use the unique command:

v2 = c(1,2,3,2,5,6,8,4,4,3)
unique(v2)
## [1] 1 2 3 5 6 8 4

We can use the unique command to remove identical rows in data frames or arrays:

dfa = data.frame(eins = c("a", "a", "aa", "a"), zwei = c("b", "b", "aa", "a"), drei = c("c", "c", "aa", "a"), vier = c("d", "d", "u", "v")) 
dfa
##   eins zwei drei vier
## 1    a    b    c    d
## 2    a    b    c    d
## 3   aa   aa   aa    u
## 4    a    a    a    v
unique(dfa)
##   eins zwei drei vier
## 1    a    b    c    d
## 3   aa   aa   aa    u
## 4    a    a    a    v

The table command

The table command can be very useful to gain insight into large data sets. Here comes an example (with a small dataset):

dat = c(rep(3,4), rep(8,3), rep(12,8))
dat
##  [1]  3  3  3  3  8  8  8 12 12 12 12 12 12 12 12
table(dat)
## dat
##  3  8 12 
##  4  3  8

The output says that “dat” includes:

The following example is from the table help page:

set.seed(77)
values = rpois(20, 2)  # Poisson distribution with mean = 2; 20 randomly generated values 

The above call of the function rpois randomly generates 20 outcomes of a Poisson distribution with mean 2.

We can look at the output in tabular form:

values
##  [1] 1 3 4 5 3 2 3 3 2 2 4 6 1 0 2 1 2 3 4 0
table(values)
## values
## 0 1 2 3 4 5 6 
## 2 3 5 5 3 1 1

Summaries for data

The summary command can be used to get information about different kinds of data sets.

We start with pure numerical data. The call of rnorm below randomly creates 100 outcomes of a normal distribution with mean 2 and standard deviation 4. We immediately calculate the summary by wrapping the summary() around:

summary(rnorm(100, 2, 4))
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## -11.6382  -0.2989   1.8385   1.7130   4.0587  11.7403

This works also for data frames, for instance for the iris dataset:

head(iris)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa
summary(iris)
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    :50  
##  versicolor:50  
##  virginica :50  
##                 
##                 
## 

More examples are listed on the summary manual page.


Note that all code snippets are designed for comprehensibility, not for efficiency!


uwe.menzel@matstat.org