How do I add a column?

Add a column to a table in base R and with dplyr’s mutate, build one column from another in the same call, and keep only the columns you need.

A column is a vector the same length as the table. Give R a vector of that length and a name, and you have a new column.

In this lesson I add a column two ways, base R first and then mutate(), build a second column out of the first in one call, and cut the table back down to the columns I want.

Step 1. Base R: name it on the left, vector on the right

p$Value <- ... creates the column if it does not exist and overwrites it if it does. The right-hand side has to be as long as the table, or length one, which R recycles down every row.

p <- data.frame(                       # a small table typed out by hand
  Ticker = c("AAA", "CCC", "DDD"),  # first column, one entry per row
  Close  = c(186.66, 416.98, 125.18)   # second column, same length as the first
)

print(ncol(p))                  # -> [1] 2
[1] 2
print(nrow(p))                  # -> [1] 3
[1] 3

Two columns, three rows. Now value ten shares of each.

p$Value <- p$Close * 10         # ten shares each, priced at the close

print(p)
  Ticker  Close  Value
1    AAA 186.66 1866.6
2    CCC 416.98 4169.8
3    DDD 125.18 1251.8
# ->   Ticker  Close  Value
# -> 1    AAA 186.66 1866.6
# -> 2    CCC 416.98 4169.8
# -> 3    DDD 125.18 1251.8

print(ncol(p))                  # -> [1] 3
[1] 3
print(names(p))                 # -> [1] "Ticker" "Close"  "Value"
[1] "Ticker" "Close"  "Value" 

p$Close * 10 is a vector of length three, one number per row, so it fits. The table went from two columns to three.

Assigning NULL removes a column.

p$Value <- NULL                 # drop the column again

print(ncol(p))                  # -> [1] 2
[1] 2
print(names(p))                 # -> [1] "Ticker" "Close"
[1] "Ticker" "Close" 

Step 2. The same move on the price file

prices.csv holds simulated daily closes and volumes for three tickers. read.csv() hands back Date as text, so I convert it with as.Date() before doing anything else.

library(dplyr)

prices <- read.csv("prices.csv")      # 120 rows, three tickers, simulated
prices$Date <- as.Date(prices$Date)   # text to real dates

print(head(prices, 3))                # first three rows, all four columns
        Date Ticker  Close  Volume
1 2026-01-02    AAA 186.66 2183494
2 2026-01-02    CCC 416.98 3117814
3 2026-01-02    DDD 125.18 7447373
# ->         Date Ticker  Close  Volume
# -> 1 2026-01-02    AAA 186.66 2183494
# -> 2 2026-01-02    CCC 416.98 3117814
# -> 3 2026-01-02    DDD 125.18 7447373

print(ncol(prices))                   # -> [1] 4
[1] 4

Traded value estimates the cash that changed hands: close times volume.

prices$Traded_Value <- prices$Close * prices$Volume   # cash traded that day

print(head(prices, 3))
        Date Ticker  Close  Volume Traded_Value
1 2026-01-02    AAA 186.66 2183494    407570990
2 2026-01-02    CCC 416.98 3117814   1300066082
3 2026-01-02    DDD 125.18 7447373    932262152
# ->         Date Ticker  Close  Volume Traded_Value
# -> 1 2026-01-02    AAA 186.66 2183494    407570990
# -> 2 2026-01-02    CCC 416.98 3117814   1300066082
# -> 3 2026-01-02    DDD 125.18 7447373    932262152

print(ncol(prices))                                  # -> [1] 5
[1] 5
prices$Traded_Value <- NULL                          # take it off again
print(ncol(prices))                                  # -> [1] 4
[1] 4

That line says prices three times. mutate() says it once.

Step 3. mutate names the table once

Inside mutate() you write column names bare, with no prices$ in front, because it already knows which table you mean.

with_traded <- prices |>                  # prices goes in, a new table comes out
  mutate(Traded_Value = Close * Volume)   # Close and Volume are columns of prices

print(head(with_traded, 3))               # the new column sits on the end
        Date Ticker  Close  Volume Traded_Value
1 2026-01-02    AAA 186.66 2183494    407570990
2 2026-01-02    CCC 416.98 3117814   1300066082
3 2026-01-02    DDD 125.18 7447373    932262152
# ->         Date Ticker  Close  Volume Traded_Value
# -> 1 2026-01-02    AAA 186.66 2183494    407570990
# -> 2 2026-01-02    CCC 416.98 3117814   1300066082
# -> 3 2026-01-02    DDD 125.18 7447373    932262152

Same numbers as the base R version. The difference is what happened to prices.

print(ncol(prices))                   # -> [1] 4
[1] 4
print(ncol(with_traded))              # -> [1] 5
[1] 5

mutate() returned a new table and left prices alone. Four columns against five. If you want the column to stick, assign the result back with prices <- prices |> mutate(...). Running a mutate() without assigning it prints a table and changes nothing.

Step 4. Two columns in one call

You can list several columns in a single mutate(), separated by commas. They are created in the order you write them, so a later one can use an earlier one.

wide <- prices |>
  mutate(
    Volume_m       = Volume / 1e6,      # shares traded, in millions
    Traded_Value_m = Close * Volume_m   # uses the column made one line above
  )

print(head(wide, 3))
        Date Ticker  Close  Volume Volume_m Traded_Value_m
1 2026-01-02    AAA 186.66 2183494 2.183494       407.5710
2 2026-01-02    CCC 416.98 3117814 3.117814      1300.0661
3 2026-01-02    DDD 125.18 7447373 7.447373       932.2622
# ->         Date Ticker  Close  Volume Volume_m Traded_Value_m
# -> 1 2026-01-02    AAA 186.66 2183494 2.183494       407.5710
# -> 2 2026-01-02    CCC 416.98 3117814 3.117814      1300.0661
# -> 3 2026-01-02    DDD 125.18 7447373 7.447373       932.2622

print(ncol(wide))                     # -> [1] 6
[1] 6

Traded_Value_m reads Volume_m, which did not exist when the call started. Check the first row: Traded_Value in Step 3 was 407570990, and 407570990 divided by a million is 407.5710. Swap the two lines around and R stops with an object not found error, because Volume_m is not there yet.

Step 5. Keep only what you need

Six columns is more than I want to look at. select() keeps the ones you name, in the order you name them.

small <- wide |>
  select(Date, Ticker, Traded_Value_m)   # keep three columns, drop the other three

print(head(small, 3))
        Date Ticker Traded_Value_m
1 2026-01-02    AAA       407.5710
2 2026-01-02    CCC      1300.0661
3 2026-01-02    DDD       932.2622
# ->         Date Ticker Traded_Value_m
# -> 1 2026-01-02    AAA       407.5710
# -> 2 2026-01-02    CCC      1300.0661
# -> 3 2026-01-02    DDD       932.2622

print(ncol(small))                    # -> [1] 3
[1] 3

transmute() does both jobs at once: it makes the new columns and throws away everything you did not mention.

tiny <- prices |>
  transmute(Ticker,                                    # carry this one through unchanged
            Traded_Value_m = Close * Volume / 1e6)     # build this one

print(head(tiny, 3))
  Ticker Traded_Value_m
1    AAA       407.5710
2    CCC      1300.0661
3    DDD       932.2622
# ->   Ticker Traded_Value_m
# -> 1    AAA       407.5710
# -> 2    CCC      1300.0661
# -> 3    DDD       932.2622

print(ncol(tiny))                     # -> [1] 2
[1] 2
print(ncol(prices))                   # -> [1] 4
[1] 4

prices still has its four columns. Nothing I did in this lesson touched it after Step 2.

Your turn

In one mutate() call, add Value holding what 100 shares are worth at the close, and Value_k holding that in thousands. Then keep only Date, Ticker and Value_k. Confirm prices still has four columns.

book <- prices |>
  mutate(
    Value   = Close * 100,      # 100 shares at the close
    Value_k = Value / 1000      # the same figure in thousands
  ) |>
  select(Date, Ticker, Value_k) # keep three columns

print(head(book, 3))
# ->         Date Ticker Value_k
# -> 1 2026-01-02   AAA  18.666
# -> 2 2026-01-02   CCC  41.698
# -> 3 2026-01-02   DDD  12.518

print(ncol(book))               # -> [1] 3
print(ncol(prices))             # -> [1] 4      <- unchanged

Every column here is built row by row on its own. Lesson 15 builds one that depends on the row before it: a return.