Data Frames
A data frame is R's table: rows are records (one shop item, one student, one day) and columns are variables (price, marks, temperature). Unlike a matrix, each column can have its own type, so a name column of text can sit next to a price column of numbers. Nearly all data analysis in R happens in data frames.
In this lesson you will create a data frame, inspect it, add and compute columns, sort and add rows, and combine two tables with merge().
Creating a data frame
data.frame() takes named vectors of the same length; each one becomes a column. Here is the stock list of a kirana (grocery) shop:
shop <- data.frame(
item = c("Rice 5kg", "Atta 10kg", "Toor dal 1kg", "Sugar 1kg", "Tea 500g"),
price = c(420, 460, 165, 48, 280),
stock = c(25, 18, 40, 60, 12)
)
shop
str(shop)
dim(shop)
names(shop)item price stock 1 Rice 5kg 420 25 2 Atta 10kg 460 18 3 Toor dal 1kg 165 40 4 Sugar 1kg 48 60 5 Tea 500g 280 12 'data.frame': 5 obs. of 3 variables: $ item : chr "Rice 5kg" "Atta 10kg" "Toor dal 1kg" "Sugar 1kg" ... $ price: num 420 460 165 48 280 $ stock: num 25 18 40 60 12 [1] 5 3 [1] "item" "price" "stock"
- The numbers 1 to 5 on the left are row names; R adds them automatically.
str()is the most useful first look at any data frame: 5 observations (rows) of 3 variables (columns), with each column's type (chrtext,numnumbers) and first values.dim()gives rows and columns;nrow()andncol()give each one alone.names()lists the column names.
Prices here are made-up practice values.
Working with columns
A data frame is a list of columns, so $ gives you a column as a vector. Because columns are vectors, vectorised arithmetic creates new columns in one line:
shop <- data.frame(
item = c("Rice 5kg", "Atta 10kg", "Toor dal 1kg", "Sugar 1kg", "Tea 500g"),
price = c(420, 460, 165, 48, 280),
stock = c(25, 18, 40, 60, 12)
)
shop$value <- shop$price * shop$stock
shop$low_stock <- shop$stock < 20
shop
sum(shop$value)
summary(shop$price)item price stock value low_stock 1 Rice 5kg 420 25 10500 FALSE 2 Atta 10kg 460 18 8280 TRUE 3 Toor dal 1kg 165 40 6600 FALSE 4 Sugar 1kg 48 60 2880 FALSE 5 Tea 500g 280 12 3360 TRUE [1] 31620 Min. 1st Qu. Median Mean 3rd Qu. Max. 48.0 165.0 280.0 274.6 420.0 460.0
Assigning to a column name that does not exist yet creates it. value is the money tied up in each item and low_stock flags items to reorder. The whole stock is worth ₹31,620. To delete a column, assign NULL: shop$low_stock <- NULL.
Sorting and adding rows
Sort rows with order() inside the row position of [rows, columns]. Add rows with rbind(), giving a one-row data frame with the same column names:
shop <- data.frame(
item = c("Rice 5kg", "Atta 10kg", "Toor dal 1kg", "Sugar 1kg", "Tea 500g"),
price = c(420, 460, 165, 48, 280),
stock = c(25, 18, 40, 60, 12)
)
shop[order(shop$price, decreasing = TRUE), ]
shop <- rbind(shop, data.frame(item = "Salt 1kg", price = 28, stock = 50))
nrow(shop)
tail(shop, 2) item price stock
2 Atta 10kg 460 18
1 Rice 5kg 420 25
5 Tea 500g 280 12
3 Toor dal 1kg 165 40
4 Sugar 1kg 48 60
[1] 6
item price stock
5 Tea 500g 280 12
6 Salt 1kg 28 50order(shop$price, decreasing = TRUE) returns the row positions from the most to the least expensive, and the empty column position after the comma keeps all columns. The row names travel with their rows, so you can see the original positions. Note that sorting does not change shop unless you assign the result back. tail() shows the last rows, just as head() shows the first.
Combining two tables with merge()
Real data is often split across tables. Orders store which customer bought which item; prices are in the stock table. merge() joins them on a shared column, like a lookup:
shop <- data.frame(
item = c("Rice 5kg", "Atta 10kg", "Sugar 1kg", "Tea 500g"),
price = c(420, 460, 48, 280)
)
orders <- data.frame(
customer = c("Aarav", "Isha", "Aarav", "Farhan"),
item = c("Tea 500g", "Rice 5kg", "Sugar 1kg", "Tea 500g"),
qty = c(2, 1, 3, 1)
)
bill <- merge(orders, shop, by = "item")
bill$amount <- bill$qty * bill$price
bill
aggregate(amount ~ customer, data = bill, FUN = sum)item customer qty price amount 1 Rice 5kg Isha 1 420 420 2 Sugar 1kg Aarav 3 48 144 3 Tea 500g Aarav 2 280 560 4 Tea 500g Farhan 1 280 280 customer amount 1 Aarav 704 2 Farhan 280 3 Isha 420
merge() matches rows where item is equal and puts the key column first. By default it keeps only items found in both tables, like an SQL inner join; all.x = TRUE keeps every order even without a price. aggregate(amount ~ customer, ...) then adds up the amounts for each customer. The ~ is a formula: "amount, grouped by customer". You will see more grouping in The apply Family.
Built-in data frames
The practice data sets that come with R are data frames, and you can use them anywhere:
str(airquality)
head(iris, 3)'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 ... 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
airquality has 153 days of New York air readings, with NA for missing values. Type ?airquality in RStudio or Positron to read what each column means.
Common mistakes
- Columns of different lengths.
data.frame(a = 1:3, b = 1:2)gives an error: every column needs one value per row. - Forgetting the comma.
shop[order(shop$price)](no comma) picks columns, not rows. Writeshop[order(shop$price), ]. - Expecting sorting to save itself. Assign the result:
shop <- shop[order(shop$price), ]. - Numbers stored as text. If
str()showschrfor a price column, convert it withas.numeric()after cleaning.
Key points
- A data frame is a table whose columns are vectors of equal length; columns can have different types.
str(),head(),dim(),names()andsummary()are your first look at any data.df$colgets a column; assigning to a new name adds a column, and assigningNULLdeletes it.- Sort rows with
df[order(df$col), ]and add rows withrbind(). merge()joins two tables on a shared column;aggregate()summarises by group.
Exercise
Create a data frame of five trains with columns train ("Shatabdi", "Duronto", "Garib Rath", "Jan Shatabdi", "Vande Bharat"), distance_km (450, 1350, 980, 520, 760) and hours (6.5, 16, 15, 8.5, 8). Add a column speed with the average speed in km/h rounded to 1 decimal place, then print the trains sorted from fastest to slowest. The numbers are invented for practice.
Show solution
trains <- data.frame(
train = c("Shatabdi", "Duronto", "Garib Rath", "Jan Shatabdi", "Vande Bharat"),
distance_km = c(450, 1350, 980, 520, 760),
hours = c(6.5, 16, 15, 8.5, 8)
)
trains$speed <- round(trains$distance_km / trains$hours, 1)
trains[order(trains$speed, decreasing = TRUE), ]train distance_km hours speed 5 Vande Bharat 760 8.0 95.0 2 Duronto 1350 16.0 84.4 1 Shatabdi 450 6.5 69.2 3 Garib Rath 980 15.0 65.3 4 Jan Shatabdi 520 8.5 61.2
Dividing one column by another works row by row, and order(..., decreasing = TRUE) returns the row positions from fastest to slowest.
Quick Quiz
1. What is the main difference between a matrix and a data frame?
Every value in a matrix has one type; each data frame column can have its own type.
2. Which function gives a compact overview of every column's type and first values?
str() shows the structure: rows, columns, types and sample values.
3. How do you sort df by the age column?
order() gives the row positions, which go before the comma.
4. What does merge(a, b, by = "id") keep by default?
The default is an inner join; use all.x = TRUE or all = TRUE to keep more rows.
Learn R Programming with a mentor
Free lessons get you started. Our Data Science & Machine Learning training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
