data.frame()
data.frame(..., row.names = NULL, check.rows = FALSE, check.names = TRUE, stringsAsFactors = FALSE) A data frame is R’s primary structure for storing rectangular data, like a spreadsheet or SQL table. Each column can hold a different data type (numeric, character, factor, logical, etc.), but all columns must have the same number of rows. This makes data frames the standard representation for datasets imported from CSV files, databases, and APIs.
Syntax
data.frame(
col1 = values1,
col2 = values2,
...
)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
... | any | , | Column name = value pairs |
row.names | NULL or character | NULL | Row names (NULL generates automatic names) |
check.rows | logical | FALSE | If TRUE, check that rows are same length |
check.names | logical | TRUE | If TRUE, check that column names are valid |
stringsAsFactors | logical | FALSE | If TRUE, convert character columns to factors |
Examples
Creating a basic data frame
# Create a simple data frame
df <- data.frame(
name = c("Alice", "Bob", "Charlie"),
age = c(25, 30, 35),
score = c(85.5, 92.0, 78.5)
)
df
# name age score
# 1 Alice 25 85.5
# 2 Bob 30 92.0
# 3 Charlie 35 78.5
# Check structure
str(df)
# 'data.frame': 3 obs. of 3 variables:
# $ name : chr "Alice" "Bob" "Charlie"
# $ age : num 25 30 35
# $ score: num 85.5 92 78.5
Accessing columns and rows
Once you have a data frame, you need to pull out specific rows, columns, or individual cells. The $ operator extracts a column as a raw vector, while [[ does the same by name or position. For rectangular subsets, [ takes two indices separated by a comma: rows first, then columns. Leaving one index blank selects all entries in that dimension.
# Access column by name
df$name
# [1] "Alice" "Bob" "Charlie"
# Access by position
df[[1]]
# [1] "Alice" "Bob" "Charlie"
# Subset rows
df[1, ]
# name age score
# 1 Alice 25 85.5
# Subset columns
df[, "age"]
# [1] 25 30 35
Adding and modifying columns
Data frames are not read-only tables. You can extend them with new columns or overwrite existing ones using the $ assignment syntax. R matches the new column’s length against the frame’s number of rows; if it is shorter, the vector recycles. Be explicit about lengths when adding derived columns to avoid silent recycling bugs, especially when the column comes from a computation whose output size depends on input size.
# Add a new column
df$city <- c("London", "Paris", "Berlin")
df
# name age score city
# 1 Alice 25 85.5 London
# 2 Bob 30 92.0 Paris
# 3 Charlie 35 78.5 Berlin
# Modify existing column
df$age <- df$age + 1
df
# name age score city
# 1 Alice 26 85.5 London
# 2 Bob 31 92.0 Paris
# 3 Charlie 36 78.5 Berlin
Common patterns
Converting to tibble (tidyverse)
Data frames work well with base R, but the tidyverse ecosystem prefers tibbles for their stricter type preservation and cleaner printing. The as_tibble() function converts a data frame in place without copying column data, so it is cheap even on large tables. Tibbles never convert strings to factors and they print a compact summary that shows column types, making them easier to scan in interactive sessions than the full print() of a base data frame.
library(tibble)
as_tibble(df)
# # A tibble: 3 × 4
# name age score city
# <chr> <dbl> <dbl> <chr>
# 1 Alice 26 85.5 London
# 2 Bob 31 92 Paris
# 3 Charlie 36 78.5 Berlin
Reading from CSV
In practice, most data frames come from reading external files rather than typing values manually. read.csv() is the base R function and produces a plain data frame. The readr package’s read_csv() returns a tibble instead, which avoids the factor conversion pitfall and parses column types automatically. For production scripts, prefer read_csv() — it is faster, handles encoding issues more cleanly, and gives you column-type feedback in the console.
# Read CSV into data frame
df <- read.csv("data.csv", stringsAsFactors = FALSE)
# Same with readr
library(readr)
df <- read_csv("data.csv")
data.frame vs tibble
data.frame() is the base R rectangular data type. tibble (from the tibble package, loaded with tidyverse) is a modern reimplementation with stricter behavior: it never converts strings to factors, it does not allow partial column name matching, and it prints compactly with a type summary. For new code, tibbles are generally preferred.
The key behavioral differences matter when migrating old code: data.frame() silently coerces character columns to factors by default in R versions before 4.0 (stringsAsFactors = TRUE was the default). From R 4.0 onward, the default changed to FALSE. If your code or a package you depend on relies on the old behavior, you may need to set stringsAsFactors = TRUE explicitly.
Data frames support partial column name matching: df$nam matches df$name if name is the only column that starts with "nam". This convenience is a source of subtle bugs as the data structure evolves, adding a second column starting with "nam" breaks the match silently. Tibbles disable partial matching entirely, df$nam returns NULL with a warning rather than silently returning a column. In new code, use exact column names and reserve $ access for known-stable column names.
Column names in a data.frame must be unique, but R does not enforce this at construction time, data.frame(x = 1, x = 2) succeeds and creates columns named x and x.1 through check.names = TRUE. When check.names = FALSE, duplicate column names are permitted, which breaks $ access and many tidyverse functions. Tools that read data from external sources (CSV files, databases) can produce duplicate names if the source data has them — inspect names with names(df) immediately after import, and use make.names(names(df), unique = TRUE) to deduplicate if needed.