{"id":13711940,"url":"https://github.com/davidsjoberg/ggsankey","last_synced_at":"2025-05-06T21:32:44.833Z","repository":{"id":43673467,"uuid":"351576887","full_name":"davidsjoberg/ggsankey","owner":"davidsjoberg","description":"Make sankey, alluvial and sankey bump plots in ggplot","archived":false,"fork":false,"pushed_at":"2024-04-04T05:22:07.000Z","size":5167,"stargazers_count":267,"open_issues_count":38,"forks_count":32,"subscribers_count":7,"default_branch":"main","last_synced_at":"2024-11-13T22:35:14.917Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/davidsjoberg.png","metadata":{"files":{"readme":"README.Rmd","changelog":null,"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}},"created_at":"2021-03-25T21:11:10.000Z","updated_at":"2024-10-30T04:11:37.000Z","dependencies_parsed_at":"2022-08-22T14:20:05.989Z","dependency_job_id":"0a0f9273-7b05-4e2e-9a6a-1e4f58d2dfc1","html_url":"https://github.com/davidsjoberg/ggsankey","commit_stats":{"total_commits":8,"total_committers":3,"mean_commits":"2.6666666666666665","dds":0.25,"last_synced_commit":"3e171a83a5364bb24df7cb2cd9203dd79b1dae29"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsjoberg%2Fggsankey","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsjoberg%2Fggsankey/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsjoberg%2Fggsankey/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsjoberg%2Fggsankey/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidsjoberg","download_url":"https://codeload.github.com/davidsjoberg/ggsankey/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252772220,"owners_count":21801878,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-08-02T23:01:13.114Z","updated_at":"2025-05-06T21:32:43.711Z","avatar_url":"https://github.com/davidsjoberg.png","language":"R","funding_links":[],"categories":["Plot layers","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  dpi = 300,\n  comment = FALSE,\n  error = FALSE,\n  warning = FALSE,\n  fig.height = 4,\n  fig.width = 8\n)\n\nlibrary(ggsankey)\nlibrary(dplyr)\nlibrary(ggplot2)\nlibrary(tidyr)\nlibrary(titanic)\nlibrary(gapminder)\n```\n\n# ggsankey\n\n\u003c!-- badges: start --\u003e\n\u003c!-- badges: end --\u003e\n\nThe goal of ggsankey is to make beautiful sankey, alluvial and sankey bump plots in `ggplot2`\n\n## Installation\n\nYou can install the development version of ggsankey from `github` with:\n\n``` r\n# install.packages(\"devtools\")\ndevtools::install_github(\"davidsjoberg/ggsankey\")\n```\n\n## How does it work\n\n[Google](https://developers.google.com/chart/interactive/docs/gallery/sankey) defines a sankey as:\n\n***A sankey diagram is a visualization used to depict a flow from one set of values to another. The things being connected are called nodes and the connections are called links. Sankeys are best used when you want to show a many-to-many mapping between two domains or multiple paths through a set of stages.***\n\nTo plot a sankey diagram with `ggsankey` each observation has a *stage* (called a discrete x-value in `ggplot`) and be part of a *node*. Furthermore, each observation needs to have instructions of which *node* it will belong to in the next *stage*.  See the image below for some clarification.\n\n```{r, echo = FALSE}\n# Ellips 1\nxmiddle \u003c- 2\ndata \u003c- tibble(x0 = xmiddle, y0 = 180, a = .2, b = 280, angle = 0)\ndata$m1 \u003c- ifelse(is.null(data$m1), 2, data$m1)\ndata$m2 \u003c- ifelse(is.null(data$m2), data$m1, data$m2)\nn_ellipses \u003c- nrow(data)\nn \u003c- 360\n\ndata \u003c- data[rep(seq_len(n_ellipses), each = n), ]\npoints \u003c- rep(seq(0, 2 * pi, length.out = n + 1)[seq_len(n)],\n              n_ellipses)\ncos_p \u003c- cos(points)\nsin_p \u003c- sin(points)\nx_tmp \u003c- abs(cos_p)^(2 / data$m1) * data$a * sign(cos_p)\ny_tmp \u003c- abs(sin_p)^(2 / data$m2) * data$b * sign(sin_p)\ndata$x \u003c- data$x0 + x_tmp * cos(data$angle) - y_tmp * sin(data$angle)\ndata$y \u003c- data$y0 + x_tmp * sin(data$angle) + y_tmp * cos(data$angle)\ndata1 \u003c- data %\u003e% \n  filter(x \u003e= xmiddle) %\u003e% \n  arrange(y)\ndata2 \u003c- data %\u003e% \n  filter(x \u003c= xmiddle) %\u003e% \n  arrange(y)\n\n# Ellips 2\nxmiddle \u003c- 3.5\ndata \u003c- tibble(x0 = xmiddle, y0 = 385, a = .07, b = 60, angle = 0)\ndata$m1 \u003c- ifelse(is.null(data$m1), 2, data$m1)\ndata$m2 \u003c- ifelse(is.null(data$m2), data$m1, data$m2)\nn_ellipses \u003c- nrow(data)\nn \u003c- 360\n\ndata \u003c- data[rep(seq_len(n_ellipses), each = n), ]\npoints \u003c- rep(seq(0, 2 * pi, length.out = n + 1)[seq_len(n)],\n              n_ellipses)\ncos_p \u003c- cos(points)\nsin_p \u003c- sin(points)\nx_tmp \u003c- abs(cos_p)^(2 / data$m1) * data$a * sign(cos_p)\ny_tmp \u003c- abs(sin_p)^(2 / data$m2) * data$b * sign(sin_p)\ndata$x \u003c- data$x0 + x_tmp * cos(data$angle) - y_tmp * sin(data$angle)\ndata$y \u003c- data$y0 + x_tmp * sin(data$angle) + y_tmp * cos(data$angle)\ndatat1 \u003c- data %\u003e% \n  filter(x \u003e= xmiddle) %\u003e% \n  arrange(y)\ndatat2 \u003c- data %\u003e% \n  filter(x \u003c= xmiddle) %\u003e% \n  arrange(y)\n\n# Ellips 3\nxmiddle \u003c- 3\ndata \u003c- tibble(x0 = xmiddle, y0 = 0, a = .15, b = 600, angle = 0)\ndata$m1 \u003c- ifelse(is.null(data$m1), 2, data$m1)\ndata$m2 \u003c- ifelse(is.null(data$m2), data$m1, data$m2)\nn_ellipses \u003c- nrow(data)\nn \u003c- 360\n\ndata \u003c- data[rep(seq_len(n_ellipses), each = n), ]\npoints \u003c- rep(seq(0, 2 * pi, length.out = n + 1)[seq_len(n)],\n              n_ellipses)\ncos_p \u003c- cos(points)\nsin_p \u003c- sin(points)\nx_tmp \u003c- abs(cos_p)^(2 / data$m1) * data$a * sign(cos_p)\ny_tmp \u003c- abs(sin_p)^(2 / data$m2) * data$b * sign(sin_p)\ndata$x \u003c- data$x0 + x_tmp * cos(data$angle) - y_tmp * sin(data$angle)\ndata$y \u003c- data$y0 + x_tmp * sin(data$angle) + y_tmp * cos(data$angle)\ndatatt1 \u003c- data %\u003e% \n  filter(x \u003e= xmiddle) %\u003e% \n  arrange(y)\ndatatt2 \u003c- data %\u003e% \n  filter(x \u003c= xmiddle) %\u003e% \n  arrange(y)\n\n# PLOT\ndf \u003c- titanic::titanic_train %\u003e%\n  as_tibble() %\u003e%\n  drop_na() %\u003e%\n  make_long(Embarked, Sex, Pclass, Survived)\n\ndf \u003c- df %\u003e%\n  dplyr::mutate(\n    shift = case_when(\n      x == \"Embarked\" \u0026 node == \"S\" ~ 300,\n      T ~ 0\n    )\n  )\n\nggplot(df, aes(x = x, next_x = next_x, node = node, next_node = next_node, fill = factor(node), label = node, shift = shift)) +\n  geom_sankey(color = \"transparent\", fill = \"transparent\") +\n  geom_path(data = data1, aes(x, y), inherit.aes = F, color = \"red\", linewidth = 1.5) +\n  geom_path(data = datat1, aes(x, y), inherit.aes = F, color = \"red\", linewidth = 1.5) +\n  geom_path(data = datatt1, aes(x, y), inherit.aes = F, color = \"red\", linewidth = 1.5) +\n  geom_sankey(node.color = \"black\", flow.color = \"black\") +\n  # geom_sankey_label(size = 3, color = \"black\", fill = \"white\") +\n  geom_path(data = data2, aes(x, y), inherit.aes = F, color = \"red\", linewidth = 1.5) +\n  geom_path(data = datat2, aes(x, y), inherit.aes = F, color = \"red\", linewidth = 1.5) +\n  geom_path(data = datatt2, aes(x, y), inherit.aes = F, color = \"red\", linewidth = 1.5) +\n  geom_text(data = tibble(x = c(3.5, 2, 3.5), y = c(510, 520, -570), label = c(\"Flow\", \"Node\", \"Stage (x)\")), aes(x, y, label = label), inherit.aes = F, color = \"red\", size = 8) +\n  scale_fill_viridis_d(drop = FALSE) +\n  scale_x_discrete(expand = scales::expand_range(.2)) +\n  theme_void(base_size = 18) +\n  labs(x = NULL) +\n  theme(legend.position = \"none\",\n        plot.title = element_text(hjust = .5)) +\n  labs(y = NULL,\n       title = \"Principal aesthetics\")\n# ggsave(\"sankey_aes.png\", dpi = 800, height = 4, width = 8)\n```\n\nHence, to use `geom_sankey` the aesthetics `x`, `next_x`, `node` and `next_node` are required. The last *stage* should point to `NA`. The aesthetics fill and color will affect both *nodes* and *flows*.\n\n\nTo plot a sankey diagram with `ggsankey` each observation has a *stage* (called a discrete x-value in `ggplot`) and be part of a *node*. Furthermore, each observation needs to have instructions of which *node* it will belong to in the next *stage*.  See the image below for some clarification.\n\n```{r, echo = FALSE}\nggplot(df, aes(x = x, next_x = next_x, node = node, next_node = next_node, fill = factor(node), label = node, shift = shift)) +\n  geom_sankey(color = \"transparent\", fill = \"transparent\") +\n  geom_sankey(node.color = \"black\", flow.color = \"black\") +\n    # node fill\n  \n  # aes fill\n  geom_text(aes(1.8, 600, label = \"fill\"), color = \"black\", inherit.aes = F, size = 8, hjust = 0) +\n  geom_curve(aes(1.85, 550, xend = 2, yend = 300), color = \"black\", inherit.aes = F, arrow = arrow(length = unit(0.1, \"inches\")), curvature = .3) +\n  geom_curve(aes(2, 550, xend = 2.2, yend = 300), color = \"black\", inherit.aes = F, arrow = arrow(length = unit(0.1, \"inches\")), curvature = -.3) +\n  \n  # aes color\n  geom_text(aes(2.8, 600, label = \"color\"), color = \"black\", inherit.aes = F, size = 8, hjust = 0) +\n  geom_curve(aes(3, 550, xend = 3, yend = 455), color = \"black\", inherit.aes = F, arrow = arrow(length = unit(0.1, \"inches\")), curvature = .3) +\n  geom_curve(aes(3.07, 550, xend = 3.2, yend = 455), color = \"black\", inherit.aes = F, arrow = arrow(length = unit(0.1, \"inches\")), curvature = -.3) +\n  \n  # aes shift\n  geom_text(aes(.55, 100, label = \"shift\"), color = \"black\", inherit.aes = F, size = 8, hjust = 0) +\n  geom_segment(aes(x = 1, xend = 1, y = -100, yend = 200), color = \"black\", inherit.aes = F, arrow = arrow(length = unit(0.1, \"inches\"))) +\n  \n  scale_fill_viridis_d(option = \"A\", drop = FALSE) +\n  scale_x_discrete(expand = scales::expand_range(.2)) +\n  theme_void(base_size = 18) +\n  labs(x = NULL) +\n  theme(legend.position = \"none\",\n        plot.title = element_text(hjust = .5)) +\n  labs(y = NULL,\n       title = \"Additional aesthetics\")\n# ggsave(\"sankey_aes.png\", dpi = 800, height = 4, width = 8)\n```\n\nTo control geometries (not changed by data) like fill, color, size, alpha etc for *nodes* and *flows* you can either choose to set a global value that affect both, or you can specify which one you want to alter. For example `node.color = 'black'` will only draw a black line around the nodes, but not the flows (links).\n\n```{r, echo = FALSE}\nggplot(df, aes(x = x, next_x = next_x, node = node, next_node = next_node, label = node, shift = shift)) +\n  geom_sankey(color = \"transparent\", fill = \"transparent\") +\n  geom_sankey(node.color = \"black\",\n              node.fill = \"#e44436ff\",\n              node.size = .5, \n              flow.alpha = .7,\n              flow.color = \"#c07a3eff\",\n              flow.fill = \"#3b345dff\") +\n  # geom_sankey_label(size = 3, color = \"black\", fill = \"white\") +\n  \n  # space\n  geom_text(data = tibble(x = c(4.4), y = c(70), label = c(\"space\")), aes(x, y, label = label), inherit.aes = F, color = \"black\", size = 6) +\n  geom_errorbar(aes(x = 4, ymin = 35, ymax = 98), inherit.aes = F, color = \"black\", linewidth = .9, width = .06) +\n  \n  # width\n  geom_text(data = tibble(x = 2, y = -545, label = c(\"width\")), aes(x, y, label = label), inherit.aes = F, color = \"black\", size = 6) +\n  geom_errorbarh(aes(xmin = 2-.05, xmax = 2+.05, y = -470), inherit.aes = F, color = \"black\", linewidth = .9, height = 30) +\n  \n  # node color\n  geom_text(aes(2.22, 500, label = \"node.color\"), color = \"black\", inherit.aes = F, size = 5, hjust = 0) +\n  geom_curve(aes(2.2, 500, xend = 2, yend = 415), color = \"black\", inherit.aes = F, arrow = arrow(length = unit(0.1, \"inches\"))) +\n  \n  # node fill\n  geom_text(aes(1.6, 600, label = \"node.fill\"), color = \"#e44436ff\", inherit.aes = F, size = 5, hjust = 0) +\n  geom_curve(aes(1.7, 560, xend = 2, yend = 300), color = \"black\", inherit.aes = F, arrow = arrow(length = unit(0.1, \"inches\")), curvature = .3) +\n\n  # flow color\n  geom_text(aes(3.92, 500, label = \"flow.color\"), color = \"#c07a3eff\", inherit.aes = F, size = 5, hjust = 0) +\n  geom_curve(aes(3.9, 500, xend = 3.65, yend = 415), color = \"black\", inherit.aes = F, arrow = arrow(length = unit(0.1, \"inches\"))) +\n  \n  # flow fill\n  geom_text(aes(3.05, 620, label = \"flow.fill\"), color = \"#3b345dff\", inherit.aes = F, size = 5, hjust = 0) +\n  geom_curve(aes(3.1, 580, xend = 3.2, yend = 400), color = \"black\", inherit.aes = F, arrow = arrow(length = unit(0.1, \"inches\")), curvature = .1) +\n\n  # flow alpha\n  geom_text(aes(3.69, -500, label = \"flow.alpha\"), color = \"black\", inherit.aes = F, size = 5, hjust = 0) +\n  geom_text(aes(3.69, -555, label = \"(Transparency)\"), color = \"black\", inherit.aes = F, size = 3, hjust = 0) +\n  geom_curve(aes(3.65, -500, xend = 3.42, yend = -220), color = \"black\", inherit.aes = F, arrow = arrow(length = unit(0.1, \"inches\")), curvature = -.5) +\n  \n  scale_fill_viridis_d(drop = FALSE) +\n  scale_x_discrete(expand = scales::expand_range(.2)) +\n  theme_void(base_size = 18) +\n  labs(x = NULL) +\n  theme(legend.position = \"none\",\n        plot.title = element_text(hjust = .5)) +\n  labs(y = NULL,\n       title = \"Control the geometries\")\n\n# ggsave(\"sankey_geom.png\", dpi = 800, height = 4, width = 8)\n```\n\n## Basic usage\n\n### geom_sankey\n\nA basic sankey plot that shows how dimensions are linked.\n\n```{r example}\ndf \u003c- mtcars %\u003e%\n  make_long(cyl, vs, am, gear, carb)\n\nggplot(df, aes(x = x, \n               next_x = next_x, \n               node = node, \n               next_node = next_node,\n               fill = factor(node))) +\n  geom_sankey() +\n  scale_fill_discrete(drop=FALSE)\n\n```\n\nAnd by adding a little pimp. \n\n* Labels with `geom_sankey_label` which places labels in the center of nodes if given the same aesthetics. \n* `ggsankey` also comes with custom minimalistic themes that can be used. Here I use `theme_sankey`.\n\n```{r sankey}\nggplot(df, aes(x = x, next_x = next_x, node = node, next_node = next_node, fill = factor(node), label = node)) +\n  geom_sankey(flow.alpha = .6,\n              node.color = \"gray30\") +\n  geom_sankey_label(size = 3, color = \"white\", fill = \"gray40\") +\n  scale_fill_viridis_d(drop = FALSE) +\n  theme_sankey(base_size = 18) +\n  labs(x = NULL) +\n  theme(legend.position = \"none\",\n        plot.title = element_text(hjust = .5)) +\n  ggtitle(\"Car features\")\n```\n\n\n### geom_alluvial\n\nAlluvial plots are very similiar to sankey plots but have no spaces between nodes and start at y = 0 instead being centered around the x-axis.\n\n```{r alluvial}\nggplot(df, aes(x = x, next_x = next_x, node = node, next_node = next_node, fill = factor(node), label = node)) +\n  geom_alluvial(flow.alpha = .6) +\n  geom_alluvial_text(size = 3, color = \"white\") +\n  scale_fill_viridis_d(drop = FALSE) +\n  theme_alluvial(base_size = 18) +\n  labs(x = NULL) +\n  theme(legend.position = \"none\",\n        plot.title = element_text(hjust = .5)) +\n  ggtitle(\"Car features\")\n```\n\n\n### geom_sankey_bump\n\nSankey bump plots is mix between bump plots and sankey and mostly useful for time series. When a group becomes larger than another it bumps above it.\n\n```{r sankey_bump}\ndf \u003c- gapminder %\u003e%\n  group_by(continent, year) %\u003e%\n  summarise(gdp = (sum(pop * gdpPercap)/1e9) %\u003e% round(0), .groups = \"keep\") %\u003e%\n  ungroup()\n\nggplot(df, aes(x = year,\n               node = continent,\n               fill = continent,\n               value = gdp)) +\n  geom_sankey_bump(space = 0, type = \"alluvial\", color = \"transparent\", smooth = 6) +\n  scale_fill_viridis_d(option = \"A\", alpha = .8) +\n  theme_sankey_bump(base_size = 16) +\n  labs(x = NULL,\n       y = \"GDP ($ bn)\",\n       fill = NULL,\n       color = NULL) +\n  theme(legend.position = \"bottom\") +\n  labs(title = \"GDP development per continent\")\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidsjoberg%2Fggsankey","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidsjoberg%2Fggsankey","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidsjoberg%2Fggsankey/lists"}