Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ Collate:
'namespace_linter.R'
'nested_ifelse_linter.R'
'nonportable_path_linter.R'
'nrow_subset_linter.R'
'numeric_leading_zero_linter.R'
'object_length_linter.R'
'object_name_linter.R'
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export(namespace_linter)
export(nested_ifelse_linter)
export(no_tab_linter)
export(nonportable_path_linter)
export(nrow_subset_linter)
export(numeric_leading_zero_linter)
export(object_length_linter)
export(object_name_linter)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* `comparison_negation_linter()` for discouraging negated comparisons when a direct negation is preferable, e.g. `!(x == y)` could be `x != y` (part of #884, @MichaelChirico).
* `terminal_close_linter()` for discouraging using `close()` to end functions (part of #884, @MichaelChirico). Such usages are not robust to errors, where `close()` will not be run as intended. Put `close()` in an `on.exit()` hook, or use {withr} to manage connections with proper cleanup.
* `print_linter()` for discouraging usage of `print()` on string literals like `print("Reached here")` or `print(paste("Found", nrow(DF), "rows."))` (#1894, @MichaelChirico).
* `nrow_subset_linter()` for discouraging usage like `nrow(subset(x, conditions))` in favor of something like `with(x, sum(conditions))` which doesn't require a full subset of `x` (part of #884, @MichaelChirico).

### Lint accuracy fixes: removing false positives

Expand Down
43 changes: 43 additions & 0 deletions R/nrow_subset_linter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#' Block usage of `nrow(subset(x, .))`
#'
#' Using `nrow(subset(x, condition))` to count the instances where `condition`
#' applies inefficiently requires doing a full subset of `x` just to
#' count the number of rows in the resulting subset.
#' There are a number of equivalent expressions that don't require the full
#' subset, e.g. `with(x, sum(condition))` (or, more generically,
#' `with(x, sum(condition, na.rm = TRUE))`).
#' The same can be said of other versions of this like
#' `nrow(DT[(condition)])` for subsetting a `data.table` or
#' `DT %>% filter(condition) %>% nrow()`.
#'
#' @examples
#' # will produce lints
#' lint(
#' text = "nrow(subset(x, is_treatment))",
#' linters = nrow_subset_linter()
#' )
#'
#' # okay
#' lint(
#' text = "with(x, sum(is_treatment, na.rm = TRUE))",
#' linters = nrow_subset_linter()
#' )
#'
#' @evalRd rd_tags("nrow_subset_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
nrow_subset_linter <- make_linter_from_xpath(
xpath = "
//SYMBOL_FUNCTION_CALL[text() = 'subset']
/parent::expr
/parent::expr
/parent::expr[expr/SYMBOL_FUNCTION_CALL[text() = 'nrow']]
",
lint_message = paste(
"Use arithmetic to count the number of rows satisfying a condition,",
"rather than fully subsetting the table and counting the resulting rows.",
"For example, replace nrow(subset(x, is_treatment))",
"with sum(x$is_treatment). NB: use na.rm = TRUE if `is_treatment` has",
"missing values."
)
)
1 change: 1 addition & 0 deletions inst/lintr/linters.csv
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ namespace_linter,correctness robustness configurable executing
nested_ifelse_linter,efficiency readability
no_tab_linter,style consistency deprecated
nonportable_path_linter,robustness best_practices configurable
nrow_subset_linter,efficiency consistency readability best_practices
numeric_leading_zero_linter,style consistency readability
object_length_linter,style readability default configurable executing
object_name_linter,style consistency default configurable executing
Expand Down
1 change: 1 addition & 0 deletions man/best_practices_linters.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/consistency_linters.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/efficiency_linters.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions man/linters.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions man/nrow_subset_linter.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/readability_linters.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions tests/testthat/test-nrow_subset_linter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
test_that("nrow_subset_linter blocks subset() cases", {
expect_lint(
"nrow(subset(x, y == z))",
rex::rex("Use arithmetic to count the number of rows satisfying a condition"),
nrow_subset_linter()
)
})