Why Boundary Rules Matter
Binning turns a numeric predictor into categories, but a list of cut points does not by itself say which bin receives a value exactly on a cut point. This matters whenever a value equals a threshold: an observation at 10 may be assigned to the interval ending at 10 or the interval beginning at 10. In R, the right argument of cut() specifies this choice.
A boundary rule must also address the ends of the full range. By default, cut() does not include the lowest boundary when intervals are right-closed, or the highest boundary when intervals are left-closed. Those observations become missing values, even though they lie exactly on a supplied break. The include.lowest argument controls this outer endpoint behavior.
How cut() Defines the Intervals
For a strictly increasing vector of breaks \(b_1,b_2,\ldots,b_k\), cut() creates \(k-1\) intervals. With right=TRUE, the intervals are right-closed: an internal break belongs to the interval on its left. With right=FALSE, they are left-closed: an internal break belongs to the interval on its right.
The default is right=TRUE. The default for include.lowest is FALSE. Consequently, with right=TRUE, the lowest break is excluded unless include.lowest=TRUE. With right=FALSE, the highest break is excluded unless include.lowest=TRUE. This argument name can be misleading: for left-closed intervals, it is the highest break that include.lowest includes.
| cut() settings | Intervals for breaks 0, 10, 20 | Internal break 10 belongs to | Outer boundary included with include.lowest=TRUE |
|---|---|---|---|
| right=TRUE | (0,10], (10,20] | (0,10] | Lowest break 0 |
| right=FALSE | [0,10), [10,20) | [10,20) | Highest break 20 |
When include.lowest=TRUE, the first interval for right=TRUE includes its lower endpoint, and the final interval for right=FALSE includes its upper endpoint. Thus the full set of intervals includes both outer breaks. It does not include values below the lowest break or above the highest break.
These expressions describe the internal intervals. With include.lowest=TRUE, add \(b_1\) to the first right-closed interval, or add \(b_k\) to the last left-closed interval. The interval labels produced by cut() reflect closure; custom labels replace those displays, so they should be chosen to match the actual boundaries.
Worked Example: Assigning Values on Internal Boundaries
A hypothetical insurer bins a numeric exposure measure using breaks 0, 10, 20, and 30. The observed values are 0, 10, 10.01, 20, and 30. Use cut() with right=TRUE and include.lowest=TRUE. Identify each value’s interval and explain the endpoint treatment.
Method. With right=TRUE, each interval includes its right endpoint. include.lowest=TRUE adds the lowest break to the first interval. The resulting intervals are [0,10], (10,20], and (20,30].
Assign the values. The value 0 is included in the first interval because include.lowest=TRUE. The value 10 is also in the first interval because right=TRUE makes 10 its included right endpoint. The value 10.01 is greater than 10 and no greater than 20, so it is in the second interval. The value 20 is in the second interval, and 30 is in the third interval.
In R, the call is cut(c(0, 10, 10.01, 20, 30), breaks=c(0, 10, 20, 30), right=TRUE, include.lowest=TRUE). The assignments, in the input order, are [0,10], [0,10], (10,20], (10,20], and (20,30].
Check. Every value is between 0 and 30 inclusive, so none is outside the breaks. Each internal boundary, 10 and 20, appears in the interval ending at that boundary. The minimum and maximum are both assigned.
Common mistake avoided. Assigning 10 to the second interval assumes left-closed intervals. That would contradict right=TRUE.
Why the Boundary Assignment Is Unambiguous
The closure convention creates a partition: neighboring intervals meet at a break, but exactly one of them includes that break. This avoids both a gap and double assignment. For right-closed intervals, a value equal to \(b_j\) satisfies the upper-end condition for the interval ending at \(b_j\), while it fails the strict lower-end condition for the next interval.
For left-closed intervals, the same boundary \(b_j\) is instead the included lower endpoint of the next interval. It fails the strict upper-end condition for the interval before it and meets the lower-end condition for the interval after it. Therefore the assignment changes with right, rather than with the value itself.
This logic applies to internal breaks. The outermost breaks need separate attention because there is no neighboring interval beyond the supplied range. include.lowest=TRUE includes the otherwise excluded outer endpoint; it does not expand the range.
Worked Example: Left-Closed Bins and the Maximum
A hypothetical service team groups resolution times into bins using breaks 0, 10, and 20 minutes. The times to classify are 0, 10, 15, and 20. Use cut() with right=FALSE and include.lowest=TRUE. Show why the default endpoint setting would produce a different result for 20.
Method. With right=FALSE, the intervals are left-closed: [0,10) and [10,20). Since include.lowest=TRUE, the highest boundary is added to the last interval, giving [10,20]. The first interval remains [0,10).
Assign the values. The value 0 is in [0,10), and 10 is not in that interval because its right endpoint is excluded. Instead, 10 is in [10,20]. The value 15 is also in [10,20], and the value 20 is included there because include.lowest=TRUE adds the highest break.
The call is cut(c(0, 10, 15, 20), breaks=c(0, 10, 20), right=FALSE, include.lowest=TRUE). In input order, the assignments are [0,10), [10,20], [10,20], and [10,20].
Check. The internal boundary 10 belongs to the interval beginning at 10, as required by right=FALSE. The minimum 0 is included by the left-closed rule. The maximum 20 is included only because include.lowest=TRUE.
Common mistake avoided. Reading include.lowest as “include the lowest endpoint in every case” would miss that, for right=FALSE, this setting includes the highest break. If include.lowest were omitted, the value 20 would be outside all intervals and cut() would return NA for it.
Checking Minimum, Maximum, and Out-of-Range Values
For explicit breaks, cut() returns NA for values outside the supplied range. It also returns NA for the outer endpoint excluded by the selected closure rule when include.lowest=FALSE. These are distinct situations: an excluded endpoint is equal to a break, while an out-of-range value lies below the first break or above the last.
A reliable check is to list the minimum, every internal cut point, the maximum, and at least one value outside each end of the range. This tests the behavior that ordinary observations between cut points cannot reveal. In R, cut() returns a factor whose levels represent the bins; NA indicates that the value was not assigned to a bin.
Worked Example: Diagnosing NA Values at Both Ends
A hypothetical analyst defines breaks at 5, 12, and 20 for a predictor with test values 4.9, 5, 12, 20, and 20.1. First use right=TRUE with the default include.lowest=FALSE, then change to include.lowest=TRUE. Identify all assignments and distinguish excluded endpoints from out-of-range values.
Method. With right=TRUE, the intervals are (5,12] and (12,20]. With the default include.lowest=FALSE, 5 is excluded. Values below 5 and above 20 are outside the range regardless of include.lowest.
Default assignments. The value 4.9 is below the lowest break, so its result is NA. The value 5 equals the excluded lower endpoint, so its result is also NA, but for a different reason. The value 12 belongs to (5,12], and 20 belongs to (12,20]. The value 20.1 is above the highest break and is NA.
The call is cut(c(4.9, 5, 12, 20, 20.1), breaks=c(5, 12, 20), right=TRUE). In input order, the results are NA, NA, (5,12], (12,20], and NA.
After changing include.lowest. With include.lowest=TRUE, the intervals cover [5,12] and (12,20]. The value 5 is now assigned to the first interval. The values 4.9 and 20.1 remain NA because include.lowest does not extend the breaks. The complete results are NA, [5,12], [5,12], (12,20], and NA.
Check. The internal value 12 belongs to the interval ending at 12 under right=TRUE. Only the assignment of 5 changes when include.lowest changes; the out-of-range values remain unassigned.
Common mistake avoided. Changing include.lowest does not “catch” all missing assignments. It affects only the appropriate outer break, not values such as 4.9 or 20.1.
Practical Use in R
When preparing bins, make the intended closure explicit rather than relying on the default. A call such as cut(x, breaks=c(0, 10, 20), right=TRUE, include.lowest=TRUE) states the boundary policy clearly. If labels are supplied, for example labels=c("lower", "upper"), those labels describe the bins but do not change the rules that determine membership.
Before using the resulting categories in a model, verify that the levels have the intended interpretation. In particular, do not infer interval closure from labels such as “low” and “high.” Record the breaks, right setting, and include.lowest setting alongside any custom labels. This makes the transformation reproducible and allows another analyst to determine exactly where boundary observations go.
Precise interval definitions are essential whenever a binned predictor will be interpreted or applied to new observations. Boundary choices are part of the feature definition, not a cosmetic detail of the R output. Testing cut points and range endpoints directly is aligned with the SOA Exam PA (Predictive Analytics) syllabus.
Check Your Understanding
For each problem, identify the interval assignments using the stated settings.
- Use breaks 2, 8, and 14 with right=TRUE and include.lowest=TRUE. Assign 2, 8, and 14. (Answer: [2,8], [2,8], and (8,14], respectively.)
- Use breaks 0, 5, and 11 with right=FALSE and include.lowest=TRUE. Assign 0, 5, and 11. (Answer: [0,5), [5,11], and [5,11], respectively.)
- Use breaks 1, 4, and 9 with right=TRUE and the default include.lowest=FALSE. What happens to 1, 4, 9, and 9.2? (Answer: 1 is NA; 4 is in (1,4]; 9 is in (4,9]; 9.2 is NA.)
- Use breaks 3, 7, and 13 with right=FALSE and the default include.lowest=FALSE. Assign 3, 7, and 13, and state why any value is missing. (Answer: 3 is in [3,7); 7 is in [7,13); 13 is NA because the highest break is excluded.)
- A data vector is c(0, 6, 12, 18, 18.1), with breaks c(0, 6, 12, 18). Using right=TRUE and include.lowest=TRUE, give the assignment for every value. (Answer: [0,6], [0,6], (6,12], (12,18], and NA, respectively.)