Two-way flags and bands, vectorised
◈ 8 cardsorders$level <- ifelse(orders$amount >= 500, "High", "Low") is a character column — table() says High 5, Low 7. cut(orders$amount, breaks = c(0, 300, 700, 1000, Inf), labels = c("Bronze", "Silver", "Gold", "Platinum"), right = FALSE) is a factor, 4 · 5 · 2 · 1; labels are one fewer than breaks. The default right = TRUE makes (lo, hi] intervals, so a value equal to the lowest break becomes NA unless include.lowest = TRUE.
The IF that works on a whole column
Module 5 flagged large orders with =IF(D2 >= 500, "High", "Low") filled down. The R twin is ifelse() — three arguments, condition · value-if-true · value-if-false — and it is vectorised, so one call does all twelve rows:
> orders$level <- ifelse(orders$amount >= 500, "High", "Low")
> orders$level
[1] "Low" "Low" "High" "Low" "High" "High" "Low" "High" "Low" "High"
[11] "Low" "Low"
> table(orders$level)
High Low
5 7
> class(orders$level)
[1] "character"
Five orders at or above $500 — 760, 1320, 510, 640, 980 — and seven below. The result is a character vector, not a factor; table() counts it all the same. An NA in the condition gives an NA in the result: ifelse(c(420, NA, 760) >= 500, "High", "Low") is "Low" NA "High".
Worked example — four bands with cut()
Module 5 banded amounts with a nested IFS (or a banded VLOOKUP with TRUE). cut() is that lookup table as one call: a vector of breaks and a vector of labels, one fewer than the breaks, because five fence posts make four intervals:
> orders$tier <- cut(orders$amount, breaks = c(0, 300, 700, 1000, Inf), labels = c("Bronze", "Silver", "Gold", "Platinum"), right = FALSE)
> table(orders$tier)
Bronze Silver Gold Platinum
4 5 2 1
> class(orders$tier)
[1] "factor"
Four under $300 (180, 250, 90, 215), five from $300 to under $700, two from $700 to under $1,000, one at $1,000 or more. cut() returns a factor whose levels are in break order — so a bar chart or a table() comes out Bronze → Platinum, not alphabetical. Get the label count wrong and R refuses:
Error in cut.default(orders$amount, breaks = c(0, 300, 700, 1000, Inf), :
number of intervals and length of 'labels' differ
Which side of the fence a value on the break lands
right = FALSE above was deliberate. The default is right = TRUE: intervals are (lo, hi] — open on the left, closed on the right — so a value exactly on a break belongs to the lower band, and a value equal to the lowest break belongs to no band at all:
> cut(c(0, 300, 301, 700), breaks = c(0, 300, 700))
[1] <NA> (0,300] (300,700] (300,700]
Levels: (0,300] (300,700]
0 became NA — it is not greater than 0. 300 went to the lower band. Two fixes, for two intentions:
> cut(c(0, 300, 301, 700), breaks = c(0, 300, 700), include.lowest = TRUE)
[1] [0,300] [0,300] (300,700] (300,700]
Levels: [0,300] (300,700]
> cut(c(0, 300, 301, 700), breaks = c(0, 300, 700), right = FALSE)
[1] [0,300) [300,700) [300,700) <NA>
Levels: [0,300) [300,700)
include.lowest = TRUE closes the first interval's left end and keeps the right-closed convention; right = FALSE flips every interval to [lo, hi) — the "300 and up is Silver" reading Module 5's IFS used — and now the top value 700 falls off unless Inf is the last break, which is why the tier call ends with Inf. Whichever you choose, say it in the data dictionary; a $300 order is a Bronze in one convention and a Silver in the other.
No orders amount sits exactly on 300, 700 or 1000, so the twelve counts are 4 · 5 · 2 · 1 either way. The rule matters on the day one does.
Type both, then read the counts
CRISP-DM: a flag or a band is data preparation → construct data, and its rule belongs in the dictionary.