powerjoin

Extensions of 'dplyr' and 'fuzzyjoin' Join Functions

https://github.com/moodymudskipper/powerjoin

Science Score: 26.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (13.3%) to scientific vocabulary

Keywords from Contributors

dbi package-creation data-model data-warehousing datawarehousing dbplyr relational-databases standardization report
Last synced: 10 months ago · JSON representation

Repository

Extensions of 'dplyr' and 'fuzzyjoin' Join Functions

Basic Info
  • Host: GitHub
  • Owner: moodymudskipper
  • License: other
  • Language: R
  • Default Branch: master
  • Homepage:
  • Size: 2.19 MB
Statistics
  • Stars: 109
  • Watchers: 3
  • Forks: 2
  • Open Issues: 11
  • Releases: 2
Created over 4 years ago · Last pushed 11 months ago
Metadata Files
Readme Changelog License

README.Rmd

---
output: github_document
---



```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.path = "man/figures/README-",
  out.width = "100%",
  tidy.opts = list(blank = FALSE)
)
options(tidyverse.quiet = TRUE)
```

# powerjoin 

{powerjoin} extends {dplyr}'s join functions.

* Make your joins safer with the `check` argument and the `check_specs()`function
* Deal with conflicting column names by combining, coalescing them etc using the `conflict` argument
* Preprocess input, for instance to select columns to join without having to repeat
key columns in the selection
* Do painless fuzzy joins thanks to a generalized `by` argument accepting formulas
* Fill unmatched values using the `fill` argument
* Operate recursive joins by providing lists of data frames to `x` and `y`
* Keep or drop key columns with more flexibility thanks to an enhanced `keep`argument

## Installation

Install CRAN version with:
``` r
install.packages("powerjoin")
```

Or development version with:

``` r
remotes::install_github("moodymudskipper/powerjoin")
```

## Now let's match penguins

```{r}
library(powerjoin)
library(tidyverse)

# toy dataset built from Allison Horst's {palmerpenguins} package and 
# Hadley Wickham's {babynames}

male_penguins <- tribble(
     ~name,    ~species,     ~island, ~flipper_length_mm, ~body_mass_g,
 "Giordan",    "Gentoo",    "Biscoe",               222L,        5250L,
  "Lynden",    "Adelie", "Torgersen",               190L,        3900L,
  "Reiner",    "Adelie",     "Dream",               185L,        3650L
)

female_penguins <- tribble(
     ~name,    ~species,  ~island, ~flipper_length_mm, ~body_mass_g,
  "Alonda",    "Gentoo", "Biscoe",               211,        4500L,
     "Ola",    "Adelie",  "Dream",               190,        3600L,
"Mishayla",    "Gentoo", "Biscoe",               215,        4750L,
)
```

## Safer joins

The `check` argument receives an object created by the `check_specs()` function,
which provides ways to handle specific input properties, its arguments
can be :

* `"ignore"` : stay silent (default except for `implicit_keys`)
* `"inform"`
* `"warn"`
* `"abort"`

We can print these defaults :

```{r}
check_specs()
```

By default it works like {dplyr}, informing in case of implicit keys, and no
further checks :

```{r, error = TRUE}
power_inner_join(
  male_penguins[c("species", "island")],
  female_penguins[c("species", "island")]
)
```

We can silence the implicit key detection and check that we have unique keys in
the right table


```{r}
check_specs(implicit_keys = "ignore", duplicate_keys_right = "abort")
```


```{r, error = TRUE}
power_inner_join(
  male_penguins[c("species", "island")],
  female_penguins[c("species", "island")],
  check = check_specs(implicit_keys = "ignore", duplicate_keys_right = "abort")
)
```

The `column_conflict` argument guarantees that you won't have columns renamed without you
knowing, you might need it most of the time, we could setup some development and
production specs for our most common joins:

```{r}
dev_specs <- check_specs(
  column_conflict = "abort",
  inconsistent_factor_levels = "inform",
  inconsistent_type = "inform"
)

prod_specs <- check_specs(
  column_conflict = "abort",
  implicit_keys = "abort"
)
```

This will save some typing :



```{r, error = TRUE, eval = FALSE}
power_inner_join(
  male_penguins,
  female_penguins,
  by = c("species", "island"),
  check = dev_specs
)
#> Error: The following columns are conflicted and their conflicts are not handled: 
#> 'name', 'flipper_length_mm', 'body_mass_g'
```

## Handle column conflict

We saw above how to fail when encountering column conflict, here we show how to
handle it.

To resolve conflicts between identically named join columns, set the `conflict`
argument to a 2 argument function (or formula) that will take as arguments the 2 conflicting 
joined columns after the join.

```{r}
df1 <- tibble(id = 1:3, value = c(10, NA, 30))
df2 <- tibble(id = 2:4, value = c(22, 32, 42))

power_left_join(df1, df2, by = "id", conflict = `+`)
```
 
Coalescing is the most common use case and we provide the functions `coalesce_xy()`
and `coalesce_yx()` to ease this task (both wrapped around `dplyr::coalesce()`).

```{r}
power_left_join(df1, df2, by = "id", conflict = coalesce_xy)

power_left_join(df1, df2, by = "id", conflict = coalesce_yx)
```

Note that the function is operating on vectors by default, not rowwise, however
we can make it work rowwise by using `rw` in the lhs of the formula.

```{r}
power_left_join(df1, df2, by = "id", conflict = ~ sum(.x, .y, na.rm = TRUE))

power_left_join(df1, df2, by = "id", conflict = rw ~ sum(.x, .y, na.rm = TRUE))
```

If you need finer control, `conflict` can also be a named list of such functions,
formulas or special values, each to be applied on the relevant pair of conflicted
columns.


## Preprocess inputs

Traditionally key columns need to be repeated when preprocessing inputs 
before a join, which is an annoyance and an opportunity for mistakes.
With {powerjoin} we can do :

```{r}
power_inner_join(
  male_penguins %>% select_keys_and(name),
  female_penguins %>% select_keys_and(female_name = name),
  by = c("species", "island")
)
```

For semi joins, just omit arguments to `select_keys_and()`: 

```{r}
power_inner_join(
  male_penguins,
  female_penguins %>% select_keys_and(),
  by = c("species", "island")
)
```

We could also aggregate on keys before the join, without the need for any
`group_by()`/`ungroup()` gymnastics :

```{r}
power_left_join(
  male_penguins %>% summarize_by_keys(male_weight = mean(body_mass_g)),
  female_penguins %>% summarize_by_keys(female_weight = mean(body_mass_g)),
  by = c("species", "island")
)
```

`pack_along_keys()` packs given columns, or all non key columns by default, into
a data frame column named by the `name` argument, it's useful to namespace the
data and avoid conflicts

```{r}
power_left_join(
  male_penguins %>% pack_along_keys(name = "m"),
  female_penguins %>% pack_along_keys(name = "f"),
  by = c("species", "island")
)
```

We have more of these, all variants of tidyverse functions :

* `nest_by_keys()` nests given columns, or all by default, if `name` is given
a single list column of data frames is created
* `complete_keys()` expands the key columns, so all combinations are present,
filling the rest of the new rows with `NA`s. Absent factor levels are expanded
as well.



These functions do not modify the data but add an attribute that will be processed
by the join function later on, so no function should be used on top of them.

## Fuzzy joins

To do fuzzy joins we use formulas in the `by` argument, in this formula we use,
`.x` and `.y` to describe the left and right tables. This is very flexible
but can be costly since a cartesian product is computed.

```{r}
power_inner_join(
    male_penguins %>% select_keys_and(male_name = name),
    female_penguins %>% select_keys_and(female_name = name),
    by = c(~.x$flipper_length_mm < .y$flipper_length_mm, ~.x$body_mass_g > .y$body_mass_g)
)
```

We might also mix fuzzy joins with regular joins :

```{r}
power_inner_join(
    male_penguins %>% select_keys_and(male_name = name),
    female_penguins %>% select_keys_and(female_name = name),
    by = c("island", ~.x$flipper_length_mm > .y$flipper_length_mm)
)
```

Finally we might want to create a column with a value used in the comparison,
in that case we will use `<-` in the formula (several times if needed)`:

```{r}
power_inner_join(
    male_penguins %>% select_keys_and(male_name = name),
    female_penguins %>% select_keys_and(female_name = name),
    by = ~ (mass_ratio <- .y$body_mass_g / .x$body_mass_g) > 1.2
)
```

## Fill unmatched values

The `fill` argument is used to specify what to fill unmatched values with,
note that missing values resulting from matches are not replaced.

```{r}
df1 <- tibble(id = 1:3)
df2 <- tibble(id = 1:2, value2 = c(2, NA), value3 = c(NA, 3))

power_left_join(df1, df2, by = "id", fill = 0)

power_left_join(df1, df2, by = "id", fill = list(value2 = 0))
```

## Join recursively

The `x` and `y` arguments accept lists of data frames so one can do :

```{r}
df1 <- tibble(id = 1, a = "foo")
df2 <- tibble(id = 1, b = "bar")
df3 <- tibble(id = 1, c = "baz")

power_left_join(list(df1, df2, df3), by = "id")

power_left_join(df1, list(df2, df3), by = "id")
```

## Enhanced `keep` argument

By default, as in *{dplyr}*, key columns are merged and given names from the
left table. In case of a fuzzy join columns that participate in a fuzzy join are
kept from both sides.

We provide additional values `"left"`, `"right"`, `"both"` and `"none"` to choose
which keys to keep or drop.

## Notes

This package supersedes the {safejoin} package which had an unfortunate homonym on CRAN and
had a suboptimal interface and implementation.

Hadley Wickham, Romain François and David Robinson are credited for their work 
in {dplyr} and {fuzzyjoin} since this package contains some code copied from these packages.


Owner

  • Name: Antoine Fabri
  • Login: moodymudskipper
  • Kind: user
  • Location: Brussels

GitHub Events

Total
  • Issues event: 3
  • Watch event: 9
  • Issue comment event: 7
  • Push event: 69
  • Pull request event: 45
  • Create event: 1
Last Year
  • Issues event: 3
  • Watch event: 9
  • Issue comment event: 7
  • Push event: 69
  • Pull request event: 45
  • Create event: 1

Committers

Last synced: about 1 year ago

All Time
  • Total Commits: 196
  • Total Committers: 5
  • Avg Commits per committer: 39.2
  • Development Distribution Score (DDS): 0.398
Past Year
  • Commits: 38
  • Committers: 1
  • Avg Commits per committer: 38.0
  • Development Distribution Score (DDS): 0.0
Top Committers
Name Email Commits
Antoine Fabri a****i@g****m 118
Kirill Müller k****r 67
Indrajeet Patil p****e@g****m 8
Luís Fonseca l****a@g****m 2
Wael Sadek w****k@g****m 1

Issues and Pull Requests

Last synced: 10 months ago

All Time
  • Total issues: 39
  • Total pull requests: 56
  • Average time to close issues: 2 months
  • Average time to close pull requests: 5 days
  • Total issue authors: 4
  • Total pull request authors: 4
  • Average comments per issue: 1.56
  • Average comments per pull request: 0.18
  • Merged pull requests: 54
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 1
  • Pull requests: 44
  • Average time to close issues: N/A
  • Average time to close pull requests: 6 days
  • Issue authors: 1
  • Pull request authors: 3
  • Average comments per issue: 0.0
  • Average comments per pull request: 0.09
  • Merged pull requests: 43
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • moodymudskipper (34)
  • karldw (2)
  • krlmlr (2)
  • luispfonseca (1)
Pull Request Authors
  • krlmlr (40)
  • moodymudskipper (13)
  • luisDVA (2)
  • luispfonseca (1)
Top Labels
Issue Labels
on hold (1)
Pull Request Labels

Packages

  • Total packages: 1
  • Total downloads:
    • cran 308 last-month
  • Total dependent packages: 0
  • Total dependent repositories: 1
  • Total versions: 2
  • Total maintainers: 1
cran.r-project.org: powerjoin

Extensions of 'dplyr' and 'fuzzyjoin' Join Functions

  • Versions: 2
  • Dependent Packages: 0
  • Dependent Repositories: 1
  • Downloads: 308 Last month
Rankings
Stargazers count: 4.1%
Dependent repos count: 24.2%
Average: 24.4%
Forks count: 27.9%
Dependent packages count: 29.0%
Downloads: 36.5%
Maintainers (1)
Last synced: 11 months ago

Dependencies

DESCRIPTION cran
  • cli * imports
  • dplyr * imports
  • generics * imports
  • glue * imports
  • lifecycle * imports
  • magrittr * imports
  • methods * imports
  • purrr * imports
  • rlang * imports
  • tibble * imports
  • tidyr * imports
  • tidyselect * imports
  • vctrs * imports
  • testthat >= 3.0.0 suggests
.github/workflows/R-CMD-check-dev.yaml actions
  • ./.github/workflows/check * composite
  • ./.github/workflows/custom/after-install * composite
  • ./.github/workflows/custom/before-install * composite
  • ./.github/workflows/dep-matrix * composite
  • ./.github/workflows/install * composite
  • ./.github/workflows/rate-limit * composite
  • ./.github/workflows/update-snapshots * composite
  • actions/checkout v3 composite
  • r-lib/actions/setup-r v2 composite
.github/workflows/R-CMD-check.yaml actions
  • ./.github/workflows/check * composite
  • ./.github/workflows/commit * composite
  • ./.github/workflows/custom/after-install * composite
  • ./.github/workflows/custom/before-install * composite
  • ./.github/workflows/git-identity * composite
  • ./.github/workflows/install * composite
  • ./.github/workflows/pkgdown-build * composite
  • ./.github/workflows/pkgdown-deploy * composite
  • ./.github/workflows/rate-limit * composite
  • ./.github/workflows/roxygenize * composite
  • ./.github/workflows/style * composite
  • ./.github/workflows/update-snapshots * composite
  • actions/checkout v3 composite
.github/workflows/check/action.yml actions
  • actions/upload-artifact main composite
  • r-lib/actions/check-r-package v2 composite
.github/workflows/install/action.yml actions
  • ./.github/workflows/get-extra * composite
  • r-lib/actions/setup-pandoc v2 composite
  • r-lib/actions/setup-r v2 composite
  • r-lib/actions/setup-r-dependencies v2 composite
.github/workflows/lock.yaml actions
  • dessant/lock-threads v2 composite
.github/workflows/pkgdown-deploy/action.yml actions
  • nick-fields/retry v2 composite
.github/workflows/pkgdown.yaml actions
  • ./.github/workflows/custom/after-install * composite
  • ./.github/workflows/custom/before-install * composite
  • ./.github/workflows/git-identity * composite
  • ./.github/workflows/install * composite
  • ./.github/workflows/pkgdown-build * composite
  • ./.github/workflows/pkgdown-deploy * composite
  • ./.github/workflows/rate-limit * composite
  • actions/checkout v3 composite
.github/workflows/pr-commands.yaml actions
  • actions/checkout v3 composite
  • r-lib/actions/pr-fetch master composite
  • r-lib/actions/pr-push master composite
  • r-lib/actions/setup-r master composite
.github/workflows/revdep.yaml actions
  • actions/checkout v3 composite
  • actions/upload-artifact main composite
  • r-lib/actions/setup-pandoc v2 composite
.github/workflows/style/action.yml actions
  • actions/cache v3 composite
.github/workflows/update-snapshots/action.yml actions
  • peter-evans/create-pull-request v4 composite
.github/workflows/commit/action.yml actions
.github/workflows/dep-matrix/action.yml actions
.github/workflows/dep-matrix-suggests/action.yml actions
.github/workflows/get-extra/action.yml actions
.github/workflows/git-identity/action.yml actions
.github/workflows/pkgdown-build/action.yml actions
.github/workflows/rate-limit/action.yml actions
.github/workflows/roxygenize/action.yml actions