Menu

Data Frames in R: Create, Inspect and Access Columns

The data frame is R's spreadsheet: named columns of equal length, each its own type. How to build one, size it up with str() and head(), and get at columns, rows, and cells.

This page includes runnable editors - edit, run, and see output instantly.

A Data Frame Is a List of Columns

A data frame is R's table: rows of observations, columns of variables. It's the spreadsheet of R, and the shape practically all real data arrives in - one row per person, per measurement, per transaction; one column per attribute.

Structurally, a data frame is a list of equal-length vectors with row/column behavior layered on top. That one fact explains most of its rules: each column has a single type (it's a vector), different columns can have different types (it's a list), and every column must be the same length (that's the "frame" part).

You build one by handing named vectors to data.frame():

Text, numbers, and logicals living side by side - the mix a matrix can't hold. In practice you'll rarely type data in by hand like this; tables usually arrive via read.csv or a database. But everything below applies identically no matter where the data frame came from.

Sizing Up a Data Frame

Never compute on a dataset you haven't looked at. R's inspection toolkit is short and worth making reflexive:

  • str() is the one to run first on any freshly loaded data: one line per column with its type and first values. It's where you catch the column of "numbers" that actually read in as character because of one stray entry.
  • nrow() / ncol() give the dimensions; names() lists the column names.
  • summary() gives per-column statistics - min/median/mean/max for numbers, counts for logicals and factors.
  • head(df) and tail(df) print the first and last six rows - indispensable once real datasets get too big to print whole. You'll see head() in action below on a built-in dataset.

Three Ways to Get a Column

Column access is the bread-and-butter operation, and R gives you three spellings:

  • people$age - the everyday form: short, readable, autocompletes in most editors.
  • people[["age"]] - identical result, but the name can come from a variable (col <- "age"; people[[col]]), which $ can't do. Prefer it inside functions.
  • people[, "age"] - matrix-style: rows before the comma (empty = all), columns after.

All three return the column as a plain vector, ready for mean(people$age). The fourth spelling in the snippet is the trap: people["age"] with single brackets returns a one-column data frame, not a vector - the same [ vs [[ distinction lists have, because a data frame is a list. If a math function complains that your input isn't numeric, check your brackets.

Rows and Cells

The [row, column] notation reaches any slice of the table:

people[2, ] is the whole second row (as a one-row data frame - rows keep their frame, since they mix types). people[2, "name"] is a single cell. A vector of row numbers pulls several rows.

The last line is the important one: a logical condition on a column selects rows - here, everyone above 28. This mask-the-rows pattern is the foundation of all data filtering in R, and it deserves its own article: see filter and subset for the full treatment, including subset() and multi-condition filters.

Adding and Removing Columns

Because a data frame is a list of columns, columns come and go by plain assignment:

Assigning to a new name adds the column; the vector must match nrow() (or be length 1, which recycles to fill every row). The second addition shows the idiomatic move: new columns computed from existing ones, using vectorized math across the whole column at once. Assigning NULL deletes a column entirely.

Column values can be overwritten the same way - people$age <- people$age + 1 ages everyone a year.

Built-in Datasets, and a Word on Tibbles

R ships with practice datasets preloaded, so you can try everything above without importing a thing. The two you'll meet in every tutorial are mtcars (32 cars, 11 numeric variables) and iris (150 flowers, 4 measurements plus a species factor):

head() earning its keep: six rows tell you the shape of the thing without flooding the screen. These datasets are ideal playgrounds - when you're unsure how some function behaves, test it on mtcars before pointing it at your own data.

One term you'll meet as soon as you touch the tidyverse: the tibble, the tidyverse's take on the data frame. Same concept, politer behavior - printing shows only the first ten rows with column types, and there's no partial name matching. Created with tibble() or returned by readr/dplyr functions:

library(tibble)
tibble(name = c("Rosa", "Ken"), age = c(30, 41))
#> # A tibble: 2 x 2
#>   name    age
#>   <chr> <dbl>
#> 1 Rosa     30
#> 2 Ken      41

Everything in this article - $, str(), adding columns, logical row masks - works on tibbles unchanged. A tibble is a data frame with extra polish; learn data frames and you've learned both.

What You Take Away

  • A data frame is a list of equal-length columns: one type per column, mixed types across the table - R's spreadsheet.
  • Inspect before you compute: str() first, then head(), summary(), nrow()/ncol()/names().
  • df$col, df[["col"]], and df[, "col"] all return a column as a vector; single-bracket df["col"] returns a data frame.
  • df[rows, cols] slices anything; a logical mask before the comma filters rows.
  • Add columns by assignment (often computed from other columns); remove with NULL; practice on mtcars.

Next up: missing values - what NA means, why it spreads through calculations, and how to handle it.

Frequently Asked Questions

How do you create a data frame in R?

Pass named, equal-length vectors to data.frame(): df <- data.frame(name = c("Rosa", "Ken"), age = c(30, 41)). Each vector becomes a column. In practice most data frames aren't typed in by hand - they arrive from read.csv() or a database - but the structure is the same.

How do you access a column in an R data frame?

Three ways: df$age (quick and readable), df[["age"]] (same result, works when the column name is stored in a variable), and df[, "age"] (matrix-style). All three return the column as a plain vector. df["age"] with single brackets returns a one-column data frame instead - a frequent source of confusion.

What does str() show for a data frame?

One line per column: its name, type, and the first few values, plus the row and column counts at the top. It's the fastest way to see whether a column read in as numeric or text, which is why str() should be the first thing you run on any freshly loaded dataset.

How do you add a column to a data frame in R?

Assign to a name that doesn't exist yet: df$score <- c(88, 75, 93). The vector must match the number of rows (or be length 1, which recycles to every row). Remove a column by assigning NULL: df$score <- NULL.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED