# Visual Art with Pi using ggplot2 & circlize

October 12, 2018
By

Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

I stumbled across this beautiful art on Washington Post article, and I wanted to try making the similar art using “digits in pi” myself using R and things I’ve learned recently.

## Get First 100000 digits of Pi

After bit of googling, I stumbled across this this site, so I decided to get first 100000 digits from below website.

However, later I discovered another site where you can download the txt file, so I could’ve used this site instead, and might have been bit simpler.

``````## Here are packages I'm going to use.
library(tidyverse)
library(tidytext) ## so I can break single digit per line
library(circlize)

df\$x <- as.character(df\$x)
df <- df %>% slice(-1:-12)  ## discard first 12 lines
df <- df %>% slice(1:1283)  ## anything after 1283 is not pi so i only want to grab 1~1283``````

## Prepping Data Frame for Visualization

Now I have first 100000 digits of pi as character, I wanted to put them into data frame where each line would contain only 1 digit.

Pretty sure there’s likely different way of doing this, but I decided I want to utilize tidytext package, that I’ve recently started to use.

Then I went bit overload on appending extra information that I thought I might utilize later to visualize… While working on below I’ve discovered that if you pick any 4 digits number you like, it will appear at least once somewhere in first 100K digits of pi! (which I thought was pretty cool…)

For example, if you are looking for sequence of 8864, then it will appear 10 times in first 100K digits of pi, and first one appears at 2384 decimal points.

``````## Below is NOT needed, but I just wanted to do bit of searching,,,
df_tidy_1 <- df_tidy_1 %>%
group_by(cur) %>%
mutate(pos_within_cur = row_number()-1) %>%
ungroup() %>%
mutate(dig_2 = cur*10 + nxt_1,
dig_3 = cur*100 + nxt_1*10 + nxt_2,
dig_4 = cur*1000 + nxt_1*100 + nxt_2*10 + nxt_3,
dig_5 = cur*10000 + nxt_1*1000 + nxt_2*100 + nxt_3*10 + nxt_4) %>%
group_by(dig_2) %>%
mutate(pos_within_dig_2 = row_number()-1) %>%
ungroup() %>% group_by(dig_3) %>%
mutate(pos_within_dig_3 = row_number()-1) %>%
ungroup() %>% group_by(dig_4) %>%
mutate(pos_within_dig_4 = row_number()-1) %>%
ungroup() %>% group_by(dig_5) %>%
mutate(pos_within_dig_5 = row_number()-1) %>%
ungroup() %>%
mutate(up_down_same = case_when(nxt_1>cur ~ "up",
nxt_1``````
``` ```
``` Having Fun with ggplot2 Now onto the fun part! Wanted to start off simple… First one is simply first 1000 digits of pi printed, but numbers 0-9 are colour coded, so that it looks bit artsy. 2nd example, I’ve replaced colour coded number with dots, but added little twist. If number are even, then circle is filled, but if numbers are odd, then it is displayed with non-filled circle. golden_ratio <- (1+sqrt(5))/2 nc <- 25 ## Number of columns i want #nr <- floor(nc/golden_ratio) ## use this if you want image wider than longer nr <- floor(nc*golden_ratio) ## Number of rows just calculated based on golden ratio. n <- nr*nc ## number of digits to be displayed on graph df_tidy_1 %>% filter(pos < n) %>% mutate(x = pos %% nc, y = floor(pos / nc)) %>% ggplot(aes(x=x, y=y, color=factor(cur))) + #geom_point(shape=19, size=5) + #geom_point(aes(shape=up_down_same)) + geom_text(aes(label=cur, color=factor(cur)), family="Helvetica", size=5) + scale_y_reverse() + theme_void() + scale_color_viridis_d(option="magma", begin=0.2, guide="none") + scale_fill_viridis_d(option="magma", begin=0.2, guide="none") + theme(panel.background = element_rect(fill="#000000")) #+  #scale_shape_manual(values=c(25,23,24), guide="none") #ggsave(filename=str_c("output/rect_",n,"_digits_of_pi_as_dots.png"), width=11, height=11*golden_ratio) ## Variation of Above df_tidy_1 %>% filter(pos < n) %>% mutate(x = pos %% nc, y = floor(pos / nc), odd = cur %% 2) %>% ggplot(aes(x=x, y=y, color=factor(cur))) + #geom_text(aes(label=cur, color=factor(cur)), family="Helvetica", size=3) + geom_point(aes(shape=factor(odd)), size=5, alpha=0.9) + scale_y_reverse() + theme_void() + scale_color_viridis_d(option="magma", begin=0.3, guide="none") + scale_shape_manual(values=c(19,21), guide="none") + ##24, 25 triangle theme(panel.background = element_rect(fill="#000000"))  #ggsave(filename=str_c("output/rect_first_",n,"digits_of_pi_dots2.png"), height=11, width=11*golden_ratio) Creating Art with connecting 2 consective digits using geom_segment This time, I wanted to utilize geom_segment to draw strings in the way that 2 consective digits are connected with strings. I love viridis palette especially magma palette, so I’ve stuck to same colour pallette for now, but I can definitely see I can play around with different colour palette. For 2nd plot, I’ve also tried to see what would happen if I’ve placed plot on polar coordinate. n_string <- 10000 ## number of items i'll show in graph df_tidy_1 %>% filter(pos < n_string) %>% mutate(x = cur + (pos/n_string) , xend = nxt_1 + (pos/n_string), odd = cur %% 2) %>% ggplot(aes(color=dig_4)) + geom_segment(aes(x=x, xend=xend, y=0,yend=1), size=0.05) + scale_color_viridis_c(option="magma", guide="none", begin=0.2) + ## i like plasma too theme_void() + theme(panel.background = element_rect(fill="#000000"))  #ggsave(file="output/100K_strings_pi_art_plasma.png", width=17, height=floor(17/golden_ratio)) df_tidy_1 %>% filter(pos < n_string) %>% mutate(x = cur + (pos/n_string) , xend = nxt_1 + (pos/n_string)) %>% ggplot(aes(color=dig_4)) + geom_segment(aes(x=x, xend=xend, y=0,yend=1), size=0.02) + scale_color_viridis_c(option="magma", guide="none", begin=0.2) + ## i like plasma too theme_void() + theme(panel.background = element_rect(fill="#000000")) + coord_polar(theta="x")  Visualizing with Circlize Using same colour schema I wanted to also try out plotting digits of pi with circo graph using circlize package. I love using chordDiagram function! It’s amazing just few lines of code, I was able to produce pretty neat chart. library(circlize) magma_pal <- viridis::magma(n=10, begin=0.2) names(magma_pal) <- c(0,1,2,3,4,5,6,7,8,9) ## par(bg = "#000000",col="#ffffff") circos.par(start.degree = 90 ) chordDiagram(df_tidy_1 %>% filter(pos<2000) %>% select(cur, nxt_1), order = c(0:9), grid.col=magma_pal, annotationTrack=c("grid","name"), directional = 1)  circos.clear() var vglnk = { key: '949efb41171ac6ec1bf7f206d57e90b8' }; (function(d, t) { var s = d.createElement(t); s.type = 'text/javascript'; s.async = true; s.src = '//cdn.viglink.com/api/vglnk.js'; var r = d.getElementsByTagName(t); r.parentNode.insertBefore(s, r); }(document, 'script')); Related ShareTweet To leave a comment for the author, please follow the link and comment on their blog: R on Chi's Impe[r]fect Blog. R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job. Want to share your content on R-bloggers? click here if you have a blog, or here if you don't. If you got this far, why not subscribe for updates from the site? Choose your flavor: e-mail, twitter, RSS, or facebook... ```
``` ```
``` Comments are closed. ```
``` Search R-bloggers Most visited articles of the week Free Springer Books during COVID19 5 Ways to Subset a Data Frame in R RStudio 1.3 Released How to write the first for loop in R The Role of the Data Scientist Date Formats in R R – Sorting a data frame by the contents of a column My Favorite dplyr 1.0.0 Features Installing R packages Sponsors // https://support.cloudflare.com/hc/en-us/articles/200169436-How-can-I-have-Rocket-Loader-ignore-my-script-s-in-Automatic-Mode- // this must be placed higher. Otherwise it doesn't work. // data-cfasync="false" is for making sure cloudflares' rocketcache doesn't interfeare with this // in this case it only works because it was used at the original script in the text widget function createCookie(name,value,days) { var expires = ""; if (days) { var date = new Date(); date.setTime(date.getTime() + (days*24*60*60*1000)); expires = "; expires=" + date.toUTCString(); } document.cookie = name + "=" + value + expires + "; path=/"; } function readCookie(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); } return null; } function eraseCookie(name) { createCookie(name,"",-1); } async function readTextFile(file) { // Helps people browse between pages without the need to keep downloading the same // ads txt page everytime. This way, it allows them to use their browser's cache. var random_number = readCookie("ad_random_number_cookie"); if(random_number == null) { var random_number = Math.floor(Math.random()*100*(new Date().getTime()/10000000000)); createCookie("ad_random_number_cookie",random_number,1) } file += '?t='+random_number; var rawFile = new XMLHttpRequest(); rawFile.onreadystatechange = function () { if(rawFile.readyState === 4) { if(rawFile.status === 200 || rawFile.status == 0) { // var allText = rawFile.responseText; // document.write(allText); document.write(rawFile.responseText); } } } rawFile.open("GET", file, false); rawFile.send(null); } // readTextFile('https://raw.githubusercontent.com/Raynos/file-store/master/temp.txt'); readTextFile("https://www.r-bloggers.com/wp-content/uploads/text-widget_anti-cache.txt"); Jobs for R usersData Analytics Auditor, Future of Audit LeadData Analytics Auditor, Future of Audit Lead @ London or NewcastleSenior Scientist, Translational Informatics @ Vancouver, BC, CanadaSenior Principal Data Scientist @ Mountain View, California, United StatesTechnical Research Analyst – New York, U.S.Movement Building AnalystInnovation Fellow python-bloggers.com (python/data-science news)Determine optimal sample sizes for business value in A/B testing, by Chris SaidMaking Pictures 3D using Context-aware Layered Depth InpaintingAdaOptAutomatically create perfect .gitignore file for your projectHow to Write a Git Commit Message, in 7 StepsPredictive Power Score: Finding predictive patterns in your datasetDocumentation+Pypi for the `teller`, a model-agnostic tool for Machine Learning explainability Full list of contributing R-bloggers ```
``` R-bloggers was founded by Tal Galili, with gratitude to the R community. Is powered by WordPress using a bavotasan.com design. Copyright © 2020 R-bloggers. All Rights Reserved. Terms and Conditions for this website var snp_f = []; var snp_hostname = new RegExp(location.host); var snp_http = new RegExp("^(http|https)://", "i"); var snp_cookie_prefix = ''; var snp_separate_cookies = false; var snp_ajax_url = 'https://www.r-bloggers.com/wp-admin/admin-ajax.php'; var snp_ajax_nonce = 'df3a1b7546'; var snp_ignore_cookies = false; var snp_enable_analytics_events = false; var snp_enable_mobile = false; var snp_use_in_all = false; var snp_excluded_urls = []; snp_excluded_urls.push(''); Never miss an update! Subscribe to R-bloggers to receive e-mails with the latest R posts. (You will not see this message again.) Click here to close (This popup will not appear again) .snp-pop-109583 .snp-theme6 { max-width: 700px;} .snp-pop-109583 .snp-theme6 h1 {font-size: 17px;} .snp-pop-109583 .snp-theme6 { color: #a0a4a9;} .snp-pop-109583 .snp-theme6 .snp-field ::-webkit-input-placeholder { color: #a0a4a9;} .snp-pop-109583 .snp-theme6 .snp-field :-moz-placeholder { color: #a0a4a9;} .snp-pop-109583 .snp-theme6 .snp-field :-ms-input-placeholder { color: #a0a4a9;} .snp-pop-109583 .snp-theme6 .snp-field input { border: 1px solid #a0a4a9;} .snp-pop-109583 .snp-theme6 .snp-field { color: #000000;} .snp-pop-109583 .snp-theme6 { background: #f2f2f2;} jQuery(document).ready(function() { }); var CaptchaCallback = function() { jQuery('.g-recaptcha').each(function(index, el) { grecaptcha.render(el, { 'sitekey' : '' }); }); }; (function(){ var corecss = document.createElement('link'); var themecss = document.createElement('link'); var corecssurl = "https://www.r-bloggers.com/wp-content/plugins/syntaxhighlighter/syntaxhighlighter3/styles/shCore.css?ver=3.0.9b"; if ( corecss.setAttribute ) { corecss.setAttribute( "rel", "stylesheet" ); corecss.setAttribute( "type", "text/css" ); corecss.setAttribute( "href", corecssurl ); } else { corecss.rel = "stylesheet"; corecss.href = corecssurl; } document.head.appendChild( corecss ); var themecssurl = "https://www.r-bloggers.com/wp-content/plugins/syntaxhighlighter/syntaxhighlighter3/styles/shThemeDefault.css?ver=3.0.9b"; if ( themecss.setAttribute ) { themecss.setAttribute( "rel", "stylesheet" ); themecss.setAttribute( "type", "text/css" ); themecss.setAttribute( "href", themecssurl ); } else { themecss.rel = "stylesheet"; themecss.href = themecssurl; } document.head.appendChild( themecss ); })(); SyntaxHighlighter.config.strings.expandSource = '+ expand source'; SyntaxHighlighter.config.strings.help = '?'; SyntaxHighlighter.config.strings.alert = 'SyntaxHighlighter\n\n'; SyntaxHighlighter.config.strings.noBrush = 'Can\'t find brush for: '; SyntaxHighlighter.config.strings.brushNotHtmlScript = 'Brush wasn\'t configured for html-script option: '; SyntaxHighlighter.defaults['pad-line-numbers'] = false; SyntaxHighlighter.defaults['toolbar'] = false; SyntaxHighlighter.all(); // Infinite scroll support if ( typeof( jQuery ) !== 'undefined' ) { jQuery( function( \$ ) { \$( document.body ).on( 'post-load', function() { SyntaxHighlighter.highlight(); } ); } ); } _stq = window._stq || []; _stq.push([ 'view', {v:'ext',j:'1:7.3.2',blog:'11524731',post:'170872',tz:'-6',srv:'www.r-bloggers.com'} ]); _stq.push([ 'clickTrackerInit', '11524731', '170872' ]); jQuery(document).ready(function (\$) { //\$( document ).ajaxStart(function() { //}); for (var i = 0; i < document.forms.length; ++i) { var form = document.forms[i]; if (\$(form).attr("method") != "get") { \$(form).append('<input type="hidden" name="jIBkaqQVHTl" value="oCAPh9pY4Iy" />'); } if (\$(form).attr("method") != "get") { \$(form).append('<input type="hidden" name="cgSbLnGDmuop" value="0ymqxuI5tUKJ" />'); } if (\$(form).attr("method") != "get") { \$(form).append('<input type="hidden" name="lbnHGIegTFXc" value="hw1t.gx[]HkX" />'); } if (\$(form).attr("method") != "get") { \$(form).append('<input type="hidden" name="ZVuvrLMsGE_Xd" value="jA92xkTbXp]@l" />'); } } \$(document).on('submit', 'form', function () { if (\$(this).attr("method") != "get") { \$(this).append('<input type="hidden" name="jIBkaqQVHTl" value="oCAPh9pY4Iy" />'); } if (\$(this).attr("method") != "get") { \$(this).append('<input type="hidden" name="cgSbLnGDmuop" value="0ymqxuI5tUKJ" />'); } if (\$(this).attr("method") != "get") { \$(this).append('<input type="hidden" name="lbnHGIegTFXc" value="hw1t.gx[]HkX" />'); } if (\$(this).attr("method") != "get") { \$(this).append('<input type="hidden" name="ZVuvrLMsGE_Xd" value="jA92xkTbXp]@l" />'); } return true; }); jQuery.ajaxSetup({ beforeSend: function (e, data) { //console.log(Object.getOwnPropertyNames(data).sort()); //console.log(data.type); if (data.type !== 'POST') return; if (typeof data.data === 'object' && data.data !== null) { data.data.append("jIBkaqQVHTl", "oCAPh9pY4Iy"); data.data.append("cgSbLnGDmuop", "0ymqxuI5tUKJ"); data.data.append("lbnHGIegTFXc", "hw1t.gx[]HkX"); data.data.append("ZVuvrLMsGE_Xd", "jA92xkTbXp]@l"); } else { data.data = data.data + '&jIBkaqQVHTl=oCAPh9pY4Iy&cgSbLnGDmuop=0ymqxuI5tUKJ&lbnHGIegTFXc=hw1t.gx[]HkX&ZVuvrLMsGE_Xd=jA92xkTbXp]@l'; } } }); }); /* <![CDATA[ */ jQuery(function(){ jQuery("ul.sf-menu").supersubs({ minWidth: 12, maxWidth: 27, extraWidth: 1 }).superfish({ delay: 100, speed: 250 }); }); /* ]]> */ ```