-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmaster_deck.html
654 lines (482 loc) · 24.3 KB
/
master_deck.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
<!DOCTYPE html>
<html lang="" xml:lang="">
<head>
<title>Data Wrangling with R</title>
<meta charset="utf-8" />
<meta name="author" content="Janith Wanniarachchi" />
<script src="libs/header-attrs-2.6/header-attrs.js"></script>
<link href="libs/remark-css-0.0.1/default.css" rel="stylesheet" />
<link href="libs/remark-css-0.0.1/default-fonts.css" rel="stylesheet" />
</head>
<body>
<textarea id="source">
class: center, middle, inverse, title-slide
# Data Wrangling with R
## RLadies Colombo April Meetup
### Janith Wanniarachchi
### Data Science Intern at Trabeya, <br/> Consultant Data Analyst at Creative Hub, <br/> BSc. Statistics (Hons.)(UG) University of Sri Jayewardenepura
---
# Hi there
* My name is Janith Wanniarachchi, I'm currently studying in University of Sri Jayewardenepura for my BSc. in Statistics and also I'm currently interning as a data scientist at Trabeya and working part time as a Consultant Data Analyst at Creative Hub.
* I have contributed to two R packages maintained by Dr. Thiyanga Thalagala, DSJobTracker and tsdataleaks. Both of them are still a work in progress and I'm still working on improving the performance of the packages.
* I have competed in several Datathons locally and have placed in Runners up and Top Finalists positions.
* That's enough about me for now, let's get right on to today's session!
---
# What is this data *wrangling*?
* Data wrangling is where we take a raw dataset and transform it into a format that fits for different tasks
* Simply it's like meal prep where we get the data ready to make the main dish (in this case it could be a model or visualization)
* There are several packages that can help with data wrangling but for today we would be scratching the surface of the dplyr package in the tidyverse ecosystem.
---
# What are we going to do today?
* I won't be teaching you a list of functions and how to use them today, instead I will be walking you through an actual scenario where these functions might be needed.
* The main outcome of this should be to have an idea about how to approach a problem and how to combine different tools that you have under your belt.
* In other news, Spotify came to Sri Lanka! Why not make a recommender system for Spotify?
* We will be using the [TidyTuesday](https://github.com/rfordatascience/tidytuesday/blob/master/data/2020/2020-01-21/readme.md) data on Spotify from 2020-01-21.
* At the end we will talk about extending this into an actual real time application.
---
## Here's the scene,
* You are a new data science intern at Spotify and you are told to work on a simple csv dataset that resembles the actual dataset.
* You are given a snapshot of the dataset from 2020 to work on and your supervisor will be making the entire user interface of the recommender system.
* The supervisor will let you make the R codes locally using the dataset that was given to you and she will on the other end work on making the functions that show the plots and tables to the user.
* Usually most of your time will be spent on data wrangling hence she entrusts you to get the job done.
---
```
## Rows: 32,833
## Columns: 23
## $ track_id <chr> "6f807x0ima9a1j3VPbc7VN", "0r7CVbZ…
## $ track_name <chr> "I Don't Care (with Justin Bieber)…
## $ track_artist <chr> "Ed Sheeran", "Maroon 5", "Zara La…
## $ track_popularity <dbl> 66, 67, 70, 60, 69, 67, 62, 69, 68…
## $ track_album_id <chr> "2oCs0DGTsRO98Gh5ZSl2Cx", "63rPSO2…
## $ track_album_name <chr> "I Don't Care (with Justin Bieber)…
## $ track_album_release_date <chr> "2019-06-14", "2019-12-13", "2019-…
## $ playlist_name <chr> "Pop Remix", "Pop Remix", "Pop Rem…
## $ playlist_id <chr> "37i9dQZF1DXcZDD7cfEKhW", "37i9dQZ…
## $ playlist_genre <chr> "pop", "pop", "pop", "pop", "pop",…
## $ playlist_subgenre <chr> "dance pop", "dance pop", "dance p…
## $ danceability <dbl> 0.748, 0.726, 0.675, 0.718, 0.650,…
## $ energy <dbl> 0.916, 0.815, 0.931, 0.930, 0.833,…
## $ key <dbl> 6, 11, 1, 7, 1, 8, 5, 4, 8, 2, 6, …
## $ loudness <dbl> -2.634, -4.969, -3.432, -3.778, -4…
## $ mode <dbl> 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1…
## $ speechiness <dbl> 0.0583, 0.0373, 0.0742, 0.1020, 0.…
## $ acousticness <dbl> 0.10200, 0.07240, 0.07940, 0.02870…
## $ instrumentalness <dbl> 0.00e+00, 4.21e-03, 2.33e-05, 9.43…
## $ liveness <dbl> 0.0653, 0.3570, 0.1100, 0.2040, 0.…
## $ valence <dbl> 0.518, 0.693, 0.613, 0.277, 0.725,…
## $ tempo <dbl> 122.036, 99.972, 124.008, 121.956,…
## $ duration_ms <dbl> 194754, 162600, 176616, 169093, 18…
```
---
# What are all these numbers?
* The entire data dictionary was found [here](https://github.com/rfordatascience/tidytuesday/blob/master/data/2020/2020-01-21/readme.md#spotify_songscsv) and you manage to jot it down as follows
* We have the id,genre and subgenre of each playlist
* the id,name and release date of each album
* finally id,name,artist and popularity of each track.
* In addition we have the following features of songs.
+ danceability : is it suitable for dancing (range: [0,1])
+ energy : how intense and active is the song (range: [0,1])
+ loudness : the overall loudness of track in dB (range: [-60,0])
+ speechiness : is there a lot of speech in it? (range: [0,1])
+ accousticness : is the song an acoustic song (range: [0,1])
+ instrumentalness : does the song have no vocal content (range: [0,1])
+ liveness : is there an audience in the song (range: [0,1])
+ valence : how positive is the song
+ key : the key of the song (CAUTION NA's means that there is no key)
+ mode : 1 if its a major or 0 if its a minor
+ tempo : BPM of the song
---
# Task No. 1 (Spot the Cool Kids)
* Your supervisor has asked you to show the top 5 popular tracks and their artists.
* That's quite a lot to do for the first task!
* Let's break it down.
* First let's select the tracks and their artist names and how popular they are. That should work right?
---
## How do we *select* column names?
```r
spotify %>%
* select(track_name,track_artist,track_popularity)
```
* The select command helps us select columns that we want, and we can give a range of columns or even the columns that we don't want to have.
* The select function works as follows.
+ The first argument is the tibble or data frame, in this case we have piped using the pipe operator
+ the rest of the arguments are the different ways of selecting the variables. For example if we want to select columnA from tibble t we would say select(t,A). Likewise if we want to select everything except column B from tibble t we would say select(t,-B).
---
Right now you know how to select the top rows of a tibble with the head function, so if you can get the tracks arranged by their popularity in ascending order then everything's all set!
This is where the arrange function comes to play. The arrange function takes up a tibble as the first input and a set of columns to order by increasing or decreasing order.
```r
spotify %>%
select(track_name,track_artist,track_popularity) %>%
* arrange(track_popularity) %>%
head(5)
```
|track_name |track_artist | track_popularity|
|:-------------------|:-------------------|----------------:|
|Siren |SUNMI | 0|
|Lollipop (Candyman) |Aqua | 0|
|Around The World |Aqua | 0|
|I'm Yours |Influencers Worship | 0|
|Talk About It |Bancali | 0|
Wait something's not right, this isn't in descending order! We have to tell the arrange function to sort them in descending order.
---
You can tell the arrange function to order in descending order by using a minus operator in front, somewhat similar to the select function.
```r
spotify %>%
select(track_name,track_artist,track_popularity) %>%
* arrange(-track_popularity) %>%
head(5)
```
|track_name |track_artist | track_popularity|
|:------------|:--------------|----------------:|
|Dance Monkey |Tones and I | 100|
|Dance Monkey |Tones and I | 100|
|ROXANNE |Arizona Zervas | 99|
|ROXANNE |Arizona Zervas | 99|
|ROXANNE |Arizona Zervas | 99|
Hold up, why is it repeating like that?
One track can be in multiple playlists! That's why! Now what do we do?
Let's select only the distinct or unique rows.
For that we can use the distinct function. The distinct function takes in a tibble as the first argument and then a set of columns to find distinctive rows or uses all columns if no arguments are given. You can see the pattern here right?
---
## Task No. 1 is completed!
Finally by using the distinct function which returns only the unique rows in the dataframe we were able to get the top 5 track names and artists.
```r
spotify %>%
select(track_name,track_artist,track_popularity) %>%
arrange(-track_popularity) %>%
* distinct() %>%
head(5) %>%
knitr::kable()
```
|track_name |track_artist | track_popularity|
|:---------------|:--------------|----------------:|
|Dance Monkey |Tones and I | 100|
|ROXANNE |Arizona Zervas | 99|
|Tusa |KAROL G | 98|
|Memories |Maroon 5 | 98|
|Blinding Lights |The Weeknd | 98|
Let's send this out to your supervisor now!
---
## Task No. 1 last final changes
Ding! Ding!
There's a new message from your supervisor on Slack. She wants this to be more presentable on the user interface by renaming the columns and removing the popularity. Well we already know how to select columns that we don't need, but what about renaming columns?
The rename function takes in pairs of arguments in the form of *new_name = old_name* so we can rename the columns that we want with that function. This is in addition to the first argument of a tibble to do all of these on.
---
Using the rename function we were able to change the names of the column names as well
```r
spotify %>%
select(track_name,track_artist,track_popularity) %>%
arrange(-track_popularity) %>%
distinct() %>%
head(5) %>%
select(-track_popularity) %>%
* rename(Song = track_name, Artist = track_artist) %>%
knitr::kable()
```
|Song |Artist |
|:---------------|:--------------|
|Dance Monkey |Tones and I |
|ROXANNE |Arizona Zervas |
|Tusa |KAROL G |
|Memories |Maroon 5 |
|Blinding Lights |The Weeknd |
---
## Recap of Task No. 1
What did we learn from trying out Task No. 1?
* select function helps us select the columns that we want
* arrange function helps us arrange the table by columns
* rename function helps us rename columns
* distinct functions helps us select only the unique rows in the table
---
# Task No. 2 (Missing the Good old days)
* Your supervisor just pointed out a funny thing in the table that you sent her It contains the most popular songs of all times!
* She is all right with it since we worked really hard on it, but she wants to show a table of the top 5 most popular songs in the past year. The snapshot we have right now is from January 2020 but our code will be used in real time even after 2021 according to your supervisor.
* First we need to make a new column indicating the year that the album was released in
```r
spotify %>%
select(track_album_release_date) %>%
head(2)
```
```
## # A tibble: 2 x 1
## track_album_release_date
## <chr>
## 1 2019-06-14
## 2 2019-12-13
```
Uh-oh, These are in a character format and you don't know how to get the year out of a character like this. Let's ask our supervisor on how to solve this.
---
## lubridate can fix your dates
Ding! Ding!
Your supervisor replied back telling you to use the function as.Date to make the column into a date and then the year function from the lubridate package.
That was quite a lot to digest!
You go through the documentation and Google online and land on a StackOverflow page to find out that this code works out to give you the years that you need.
```r
*as.Date(spotify$track_album_release_date) %>% lubridate::year()
```
Before you send this out to your supervisor you want to make sure that the code that you wrote is correct, so you start matching with the documentation to get a rough idea about what happens.
1. You create a vector of Dates from a vector of characters
2. You get the year as a number from the year function in lubridate
---
## How do we modify the existing columns?
We have to mutate the column just like the Covid19 virus is mutating by modifying the existing traits. The format goes along as new_column_name = modification. The modification should give out a vector that fits the number of rows in the dataset
```r
spotify %>%
* mutate(release_year = lubridate::year(as.Date(track_album_release_date))) %>%
select(release_year) %>%
head(2)
```
```
## # A tibble: 2 x 1
## release_year
## <dbl>
## 1 2019
## 2 2019
```
Success! We managed to get the years out. Now we need to get the maximum of these years.
```r
spotify_with_year <- spotify %>%
mutate(release_year = lubridate::year(as.Date(track_album_release_date)))
latest_year <- max(spotify_with_year$release_year,na.rm = TRUE)
```
---
## How can we filter the songs that we want?
Simple! we use the filter function to filter out the rows that we need. We have to a logical condition as an argument to filter and you can combine different conditions using logical operators like &,|, ~ etc.
```r
spotify_with_year %>%
* filter(release_year == 2019) %>%
select(track_name,release_year) %>%
head(3)
```
```
## # A tibble: 3 x 2
## track_name release_year
## <chr> <dbl>
## 1 I Don't Care (with Justin Bieber) - Loud Luxury Remix 2019
## 2 Memories - Dillon Francis Remix 2019
## 3 All the Time - Don Diablo Remix 2019
```
Great! Now we need to plug these into our previous code and we are good to go!
---
## Task 2 is done and dusted
```r
spotify_with_year %>%
filter(release_year == 2019) %>%
select(track_name,track_artist,track_popularity) %>%
arrange(-track_popularity) %>%
distinct() %>%
head(5) %>%
select(-track_popularity) %>%
rename(Song = track_name, Artist = track_artist)
```
|Song |Artist |
|:---------------|:--------------|
|Dance Monkey |Tones and I |
|ROXANNE |Arizona Zervas |
|Tusa |KAROL G |
|Memories |Maroon 5 |
|Blinding Lights |The Weeknd |
---
## Recap of Task No. 2
What did we learn from trying out Task No.2 ?
* as.Date function converts character columns to Date columns
* year function from lubridate package gets the year from a Date column
* mutate function mutates existing columns to create new ones
* filter function helps to filter the rows based on a condition
---
# Task No. 3 (Playlists for your moods)
The supervisor has one final task for you.
You need to give the average scores for energy, danceability, instrumentalness, valence for each playlist to so that the supervisor can plus that dataset into her Plotly Radar plot function.
This is going to be a tough one! You sit down and sketch out how the final dataset should look like and this is what you got,
|playlist_genre | avg_energy| avg_dance| avg_instu| avg_valen|
|:--------------|----------:|---------:|---------:|---------:|
|some name 1 | 0.5| 0.2| 0.7| 0.5|
|some name 2 | 0.4| 0.7| 0.3| 0.8|
So now we know what sort of outcome we expect at the end. Now the question remains how do you group these playlists together
---
## Understanding group_by
We can use the group_by function in dplyr for this. But the group_by function can be tricky sometimes so you dig a bit deeper and get the following idea.
* group_by function literally groups rows with the same combination of values together
* any mutate function that is applied will apply within the group
---
With that in your head you first write out the group_by command and decide to be adventurous and mutate a new column for each of the averages.
```r
spotify %>%
* group_by(playlist_genre) %>%
mutate(avg_energy = mean(energy,na.rm=TRUE),avg_dance = mean(danceability,na.rm=TRUE),
avg_instu = mean(instrumentalness,na.rm=TRUE),avg_valen = mean(valence,na.rm=TRUE)) %>%
rename(Genre = playlist_genre,Energetic = avg_energy, Dance = avg_dance,
Instrumentals = avg_instu, Upbeat = avg_valen) %>%
select(Genre,Energetic,Dance,Instrumentals, Upbeat) %>%
head(4)
```
```
## # A tibble: 4 x 5
## # Groups: Genre [1]
## Genre Energetic Dance Instrumentals Upbeat
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 pop 0.701 0.639 0.0599 0.504
## 2 pop 0.701 0.639 0.0599 0.504
## 3 pop 0.701 0.639 0.0599 0.504
## 4 pop 0.701 0.639 0.0599 0.504
```
Wait that doesn't look right, We need only one row per genre. Do we have to call distinct again? Or is there an easier way than having to mutate -> select -> distinct?
---
## Task No. 3 is over!
Your supervisor noticed what you were typing on your laptop and gave a tip to Google up on the function summarize to make the code more readable and safer.
And now after digging on the summarize function, you realize that by simply replacing mutate with summarize your code now looks like this.
```r
spotify %>%
group_by(playlist_genre) %>%
* summarize(avg_energy = mean(energy,na.rm=TRUE),avg_dance = mean(danceability,na.rm=TRUE),
* avg_instu = mean(instrumentalness,na.rm=TRUE),avg_valen = mean(valence,na.rm=TRUE)) %>%
rename(Genre = playlist_genre,Energetic = avg_energy, Dance = avg_dance,
Instrumentals = avg_instu, Upbeat = avg_valen) %>%
head(5)
```
|Genre | Energetic| Dance| Instrumentals| Upbeat|
|:-----|---------:|---------:|-------------:|---------:|
|edm | 0.8024759| 0.6550409| 0.2185779| 0.4006555|
|latin | 0.7083125| 0.7132873| 0.0444470| 0.6055104|
|pop | 0.7010278| 0.6393017| 0.0598763| 0.5035210|
|r&b | 0.5909343| 0.6701793| 0.0289203| 0.5312306|
|rap | 0.6507084| 0.7183528| 0.0759972| 0.5050900|
---
## Recap of Task No. 3
What did we learn from trying out Task No. 3?
* group_by helps to group the dataset into groups based on the values of a column
* summarize helps to make a summary of a grouped / ungrouped dataset to get summary statistics
---
# Summary
## What did we learn today?
* Data wrangling is kind of like meal prep where you prepare your ingredients(the raw data) for the final dish(the model or visualization)
* There are several functions in different packages such as filter,mutate,select,arrange,group_by,summarize,distinct from the dplyr package and even date functions from the lubridate package that help to wrangle data into the format we need.
## Where to go from here?
* Get the Spotify Developer Account and access the APIs to create a dataset that is upto date and matches your style.
* Create an R shiny dashboard that can show interactive graphs of all the artists and where they fall on different indexes which are based on how positive the song is and so on.
---
# Thank you!
## Where to find me?
* Twitter: @janithwanni
* Github: @janithwanni
* Linkedin: Janith Wanniarachchi
</textarea>
<style data-target="print-only">@media screen {.remark-slide-container{display:block;}.remark-slide-scaler{box-shadow:none;}}</style>
<script src="https://remarkjs.com/downloads/remark-latest.min.js"></script>
<script>var slideshow = remark.create({
"highlightStyle": "github",
"highlightLines": true,
"countIncrementalSlides": false
});
if (window.HTMLWidgets) slideshow.on('afterShowSlide', function (slide) {
window.dispatchEvent(new Event('resize'));
});
(function(d) {
var s = d.createElement("style"), r = d.querySelector(".remark-slide-scaler");
if (!r) return;
s.type = "text/css"; s.innerHTML = "@page {size: " + r.style.width + " " + r.style.height +"; }";
d.head.appendChild(s);
})(document);
(function(d) {
var el = d.getElementsByClassName("remark-slides-area");
if (!el) return;
var slide, slides = slideshow.getSlides(), els = el[0].children;
for (var i = 1; i < slides.length; i++) {
slide = slides[i];
if (slide.properties.continued === "true" || slide.properties.count === "false") {
els[i - 1].className += ' has-continuation';
}
}
var s = d.createElement("style");
s.type = "text/css"; s.innerHTML = "@media print { .has-continuation { display: none; } }";
d.head.appendChild(s);
})(document);
// delete the temporary CSS (for displaying all slides initially) when the user
// starts to view slides
(function() {
var deleted = false;
slideshow.on('beforeShowSlide', function(slide) {
if (deleted) return;
var sheets = document.styleSheets, node;
for (var i = 0; i < sheets.length; i++) {
node = sheets[i].ownerNode;
if (node.dataset["target"] !== "print-only") continue;
node.parentNode.removeChild(node);
}
deleted = true;
});
})();
(function() {
"use strict"
// Replace <script> tags in slides area to make them executable
var scripts = document.querySelectorAll(
'.remark-slides-area .remark-slide-container script'
);
if (!scripts.length) return;
for (var i = 0; i < scripts.length; i++) {
var s = document.createElement('script');
var code = document.createTextNode(scripts[i].textContent);
s.appendChild(code);
var scriptAttrs = scripts[i].attributes;
for (var j = 0; j < scriptAttrs.length; j++) {
s.setAttribute(scriptAttrs[j].name, scriptAttrs[j].value);
}
scripts[i].parentElement.replaceChild(s, scripts[i]);
}
})();
(function() {
var links = document.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
if (/^(https?:)?\/\//.test(links[i].getAttribute('href'))) {
links[i].target = '_blank';
}
}
})();
// adds .remark-code-has-line-highlighted class to <pre> parent elements
// of code chunks containing highlighted lines with class .remark-code-line-highlighted
(function(d) {
const hlines = d.querySelectorAll('.remark-code-line-highlighted');
const preParents = [];
const findPreParent = function(line, p = 0) {
if (p > 1) return null; // traverse up no further than grandparent
const el = line.parentElement;
return el.tagName === "PRE" ? el : findPreParent(el, ++p);
};
for (let line of hlines) {
let pre = findPreParent(line);
if (pre && !preParents.includes(pre)) preParents.push(pre);
}
preParents.forEach(p => p.classList.add("remark-code-has-line-highlighted"));
})(document);</script>
<script>
slideshow._releaseMath = function(el) {
var i, text, code, codes = el.getElementsByTagName('code');
for (i = 0; i < codes.length;) {
code = codes[i];
if (code.parentNode.tagName !== 'PRE' && code.childElementCount === 0) {
text = code.textContent;
if (/^\\\((.|\s)+\\\)$/.test(text) || /^\\\[(.|\s)+\\\]$/.test(text) ||
/^\$\$(.|\s)+\$\$$/.test(text) ||
/^\\begin\{([^}]+)\}(.|\s)+\\end\{[^}]+\}$/.test(text)) {
code.outerHTML = code.innerHTML; // remove <code></code>
continue;
}
}
i++;
}
};
slideshow._releaseMath(document);
</script>
<!-- dynamically load mathjax for compatibility with self-contained -->
<script>
(function () {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-MML-AM_CHTML';
if (location.protocol !== 'file:' && /^https?:/.test(script.src))
script.src = script.src.replace(/^https?:/, '');
document.getElementsByTagName('head')[0].appendChild(script);
})();
</script>
</body>
</html>