{"id":14067323,"url":"https://github.com/daranzolin/hacksaw","last_synced_at":"2026-01-17T11:36:33.254Z","repository":{"id":129154057,"uuid":"266708089","full_name":"daranzolin/hacksaw","owner":"daranzolin","description":"Extra tidyverse-like functionality ","archived":false,"fork":false,"pushed_at":"2021-04-08T01:51:01.000Z","size":191,"stargazers_count":34,"open_issues_count":4,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-30T01:11:59.199Z","etag":null,"topics":["data-manipulation","r"],"latest_commit_sha":null,"homepage":"","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/daranzolin.png","metadata":{"files":{"readme":"README.Rmd","changelog":"NEWS.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-25T07:04:21.000Z","updated_at":"2025-03-04T22:19:07.000Z","dependencies_parsed_at":"2023-04-26T15:16:09.934Z","dependency_job_id":null,"html_url":"https://github.com/daranzolin/hacksaw","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/daranzolin/hacksaw","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daranzolin%2Fhacksaw","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daranzolin%2Fhacksaw/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daranzolin%2Fhacksaw/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daranzolin%2Fhacksaw/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/daranzolin","download_url":"https://codeload.github.com/daranzolin/hacksaw/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daranzolin%2Fhacksaw/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28507853,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T10:25:30.148Z","status":"ssl_error","status_checked_at":"2026-01-17T10:25:29.718Z","response_time":85,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-manipulation","r"],"created_at":"2024-08-13T07:05:32.416Z","updated_at":"2026-01-17T11:36:33.218Z","avatar_url":"https://github.com/daranzolin.png","language":"R","funding_links":[],"categories":["R"],"sub_categories":[],"readme":"---\noutput: github_document\n---\n\n\u003c!-- README.md is generated from README.Rmd. Please edit that file --\u003e\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#\u003e\",\n  fig.path = \"man/figures/README-\",\n  out.width = \"100%\"\n)\n```\n\n# hacksaw\n\n\u003c!-- badges: start --\u003e\n![](https://camo.githubusercontent.com/ea6e0ff99602c3563e3dd684abf60b30edceaeef/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6966656379636c652d6578706572696d656e74616c2d6f72616e67652e737667)\n![CRAN log](http://www.r-pkg.org/badges/version/hacksaw)\n![](http://cranlogs.r-pkg.org/badges/grand-total/hacksaw)\n[![Travis build status](https://travis-ci.org/daranzolin/hacksaw.svg?branch=master)](https://travis-ci.com/daranzolin/hacksaw)\n\u003c!-- badges: end --\u003e\n\nhacksaw is as an adhesive between various dplyr and purrr operations, with some extra tidyverse-like functionality (e.g. keeping NAs, shifting row values) and shortcuts (e.g. filtering patterns, casting, plucking, etc.).\n\n## Installation\n\nYou can install the released version of hacksaw from CRAN with:\n\n``` r\ninstall.packages(\"hacksaw\")\n```\n\nOr install the development version from GitHub with:\n\n```r\nremotes::install_github(\"daranzolin/hacksaw\")\n```\n\n\n## Split operations\n\nhacksaw's assortment of split operations recycle the original data frame. This is useful when you want to run slightly different code on the same object multiple times (e.g. assignment) or you want to take advantage of some list functionality (e.g. purrr, `lengths()`, `%-\u003e%`, etc.). \n\nThe useful`%\u003c-%` and `%-\u003e%` operators are re-exported from [the zeallot package.](https://github.com/r-lib/zeallot)\n\n### filter\n\n```{r warning=FALSE, message=FALSE}\nlibrary(hacksaw)\nlibrary(tidyverse)\n\niris %\u003e% \n  filter_split(\n    large_petals = Petal.Length \u003e 5.1,\n    large_sepals = Sepal.Length \u003e 6.4\n  ) %\u003e% \n  map(summary)\n```\n\n### select\n\nInclude multiple columns and select helpers within `c()`:\n\n```{r}\niris %\u003e% \n  select_split(\n    sepal_data = c(Species, starts_with(\"Sepal\")),\n    petal_data = c(Species, starts_with(\"Petal\"))\n  ) %\u003e% \n  str()\n```\n\n### count\n\nCount across multiple variables:\n\n```{r}\nmtcars %\u003e% \n  count_split(\n    cyl,\n    carb,\n    gear\n    )\n```\n\n\n### rolling_count_split\n\nRolling counts, left-to-right\n\n```{r}\nmtcars %\u003e% \n  rolling_count_split(\n    cyl,\n    carb,\n    gear\n    )\n```\n\n### distinct\n\nEasily get the unique values of multiple columns:\n\n```{r}\nstarwars %\u003e% \n  distinct_split(skin_color, eye_color, homeworld) %\u003e% \n  str() # lengths() is also useful\n```\n\n### mutate\n\n```{r}\niris %\u003e% \n  mutate_split(\n    Sepal.Length2 = Sepal.Length * 2,\n    Sepal.Length3 = Sepal.Length * 3\n  ) %\u003e% \n  str()\n```\n\n### group_by\n\nSeparate groups:\n\n```{r}\nmtcars %\u003e% \n  group_by_split(cyl, gear, am, across(c(cyl, gear))) %\u003e% \n  map(tally, wt = vs)\n```\n\n### rolling_group_by_split\n\nRolling groups, left-to-right:\n\n```{r}\nmtcars %\u003e% \n  rolling_group_by_split(\n    cyl, \n    carb, \n    gear\n  ) %\u003e% \n  map(summarize, mean_mpg = mean(mpg))\n```\n\n### nest_by\n\n```{r}\nmtcars %\u003e%\n    nest_by_split(cyl, gear) %\u003e%\n    map(mutate, model = list(lm(mpg ~ wt, data = data)))\n```\n\n### rolling_nest_by\n\n```{r}\nmtcars %\u003e%\n    rolling_nest_by_split(cyl, gear) %\u003e%\n    map(mutate, model = list(lm(mpg ~ wt, data = data)))\n```\n\n\n### transmute \n\n```{r}\niris %\u003e% \n  transmute_split(Sepal.Length * 2, Petal.Width + 5) %\u003e% \n  str()\n```\n\n### slice\n\n```{r}\niris %\u003e% \n  slice_split(1:10, 11:15, 30:50) %\u003e% \n  str()\n```\n\nUse the `var_max` and `var_min` helpers to easily get minimum and maximum values of a variable:\n\n```{r}\niris %\u003e% \n  slice_split(\n    largest_sepals = var_max(Sepal.Length, 4),\n    smallest_sepals = var_min(Sepal.Length, 4)\n  )#\n```\n\n### precision_split\n\n`precision_split` splits the mtcars data frame into two: one with mpg greater than 20, one with mpg less than 20:\n\n```{r}\nmtcars %\u003e% \n  precision_split(mpg \u003e 20) %-\u003e% c(lt20mpg, gt20mpg)\n\nstr(gt20mpg)\nstr(lt20mpg)\n```\n\n\n### eval_split\n\nEvaluate any expression:\n\n```{r}\nmtcars %\u003e% \n  eval_split(\n    select(hp, mpg),\n    filter(mpg \u003e 25),\n    mutate(pounds = wt*1000)\n  ) %\u003e% \n  str()\n```\n\n## Casting\n\nTired of `mutate(var = as.[character|numeric|logical](var))`? \n\n```{r}\nstarwars %\u003e% cast_character(height, mass) %\u003e% str(max.level = 2) \niris %\u003e% cast_character(contains(\".\")) %\u003e% str(max.level = 1)\n```\n\nhacksaw also includes `cast_numeric` and `cast_logical`.\n\n## Keeping NAs\n\nThe reverse of `tidyr::drop_na`, strangely omitted in the original tidyverse. \n\n```{r}\ndf \u003c- tibble(x = c(1, 2, NA, NA, NA), y = c(\"a\", NA, \"b\", NA, NA))\ndf %\u003e% keep_na()\ndf %\u003e% keep_na(x)\ndf %\u003e% keep_na(x, y)\n```\n\n## Coercive joins\n\nI never care if my join keys are incompatible. The `*_join2` suite of functions coerce either the left or right table accordingly.\n\n```{r}\ndf1 \u003c- tibble(x = 1:10, b = 1:10, y = letters[1:10])\ndf2 \u003c- tibble(x = as.character(1:10), z = letters[11:20])\nleft_join2(df1, df2)\n```\n\n## Shifting row values\n\nShift values across rows in either direction. Sometimes useful when importing irregularly-shaped tabular data.\n\n```{r}\ndf \u003c- tibble(\n  s = c(NA, 1, NA, NA),\n  t = c(NA, NA, 1, NA),\n  u = c(NA, NA, 2, 5),\n  v = c(5, 1, 9, 2),\n  x = c(1, 5, 6, 7),\n  y = c(NA, NA, 8, NA),\n  z = 1:4\n)\ndf\n\nshift_row_values(df)\nshift_row_values(df, at = 1:3)\nshift_row_values(df, at = 1:2, .dir = \"right\")\n\n```\n\n## Filtering, keeping, and discarding patterns\n\nA wrapper around `filter(grepl(..., var))`:\n\n```{r}\nstarwars %\u003e% \n  filter_pattern(homeworld, \"oo\") %\u003e% \n  distinct(homeworld)\n```\n\nUse `keep_pattern` and `discard_pattern` for lists and vectors.\n\n## Plucking values\n\nA wrapper around `x[p][i]`:\n\n```{r}\ndf \u003c- tibble(\n  id = c(1, 1, 1, 2, 2, 2, 3, 3),\n  tested = c(\"no\", \"no\", \"yes\", \"no\", \"no\", \"no\", \"yes\", \"yes\"),\n  year = c(2015:2017, 2010:2012, 2019:2020)\n) \n\ndf %\u003e% \n  group_by(id) %\u003e%\n  mutate(year_first_tested = pluck_when(year, tested == \"yes\"))\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaranzolin%2Fhacksaw","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaranzolin%2Fhacksaw","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaranzolin%2Fhacksaw/lists"}