1 Introduction
When Nolan and Temple Lang [2010]
wrote their seminal paper on the role of computing skills in statistics and statistics curricula, they noted the rapid change in the skills needed by practicing statisticians. It would no longer be sufficient for statisticians to learn computing only as a collection of numerical methods or specialized statistical algorithms, such as Markov chain Monte Carlo or methods to draw pseudorandom numbers. Statisticians now face large quantities of data, often in new forms like text or networks, and this data must be obtained—such as from Web services or databases—then managed, manipulated in complex ways, and visualized. All of this requires computational skills. They argued that students, once armed with a solid computational base, will be much better prepared to adapt to the wide range of problems they will see on the job—and these computational skills will also give them new ways to explore and understand the statistical concepts they learn.
Their premise has only become more true in the intervening years. As the conversation shifts to “data science” and companies apply statistical thinking to an ever wider range of problems, traditional statistical procedures become a smaller part of the statistician’s day: now, statisticians must use their statistical skills to determine what data is needed to answer a question, then use their computational skills to acquire the data from disparate sources across the company, integrate it into a useful form, conduct exploratory analyses and visualizations to understand the data’s full complexity, and only then use statistical procedures to draw conclusions. All of this requires statisticians to build a broad computational toolbox—databases and data scraping tools, interactive visualizations, processing pipelines for different data formats and types—and to learn the basic concepts they need so they can quickly adapt as new tools become available.
Though Nolan and Temple Lang [2010] only touched briefly on reproducible data analysis, the past ten years have also demonstrated how important it can be for statistical analysis to be reproducible. Reproducibility is a computational skill: it requires a written program that performs all steps from obtaining raw data to reporting final results, so that any reasonably skilled colleague could rerun the analysis and verify the results—or substitute new data to see what changes. It requires new ways of thinking about data analysis, and skills with tools like knitr [Xie, 2015] and the command line for automating a pipeline of scripts, analyses, and results. These skills will only become more important as scientific fields grapple with reproducibility and encourage more open disclosure of code and data.
In this paper, we argue that though these computational skills are important, for some statisticians they are only a fraction of what is now needed. Many practicing statisticians now find themselves delivering not analyses—in the form of reports or presentations on some statistical analysis—but products that are implemented and used continually. In academia, these products might be R packages implementing a newly developed statistical method, implemented so that others can apply the same method to their own problems. In industry, these products could be new methods to detect fraud or improve advertising in a large online service, implemented so that the statistical method is being used continuously as new data arrives and new decisions have to be made. In either case, the product is often a large and complex piece of software with a codebase developed by a team over many months, and it’s never truly “done”: it must be maintained and updated as conditions change and new requirements are placed on it.
To build and maintain these products, statisticians need additional skills. Structuring a large and complex codebase so it can be easily understood requires principles from software engineering; building code with a team requires version control systems and collaboration skills; implementing new statistical methods for large and complicated data requires a firm understanding of algorithms and data structures so the resulting code will be efficient. And everything must be welltested and debugged so colleagues, bosses, and users can have confidence in the results. This type of skills are less important for a oneoff data analysis (though they may help), but they are crucial for the tasks statisticians face as they put their expertise into practice as part of longrunning systems and widely used products.
In this paper, we describe our efforts to design a graduatelevel course in statistical computing based on these principles. We have taught the course for five years, evolving each year as we have discovered that the course pedagogy is inextricable from its content: for students to learn complex computational skills, we must give them regular practice with these skills and rapid feedback on their performance. We set out the skills we aim to teach and the strategies we use to teach them, and argue that these skills are becoming increasingly important for graduatelevel statisticians and cannot be left to others to fill in. Computation has only grown in importance in the ten years since Nolan and Temple Lang issued their call to action, and we expect it will only become more important in the ten years to come.
2 Role of Computing in Statistics and Data Science
2.1 Data Cleaning, Data Analysis, and Report Preparation
While we do not attempt to provide a complete history of computing in statistics, some themes are worth mentioning. With the rise of S and its opensource counterpart R
[R Core Team, 2019], along with commercial packages like SAS, computing became firmly entrenched in statistics and was widely adopted—no longer did statistical methods have to be avoided because of the inconvenience of calculation. A large ecosystem of statistical methods implemented for the computer soon developed, and statisticians could interactively explore data and build up an analysis piece by piece.In recent years there has again been a new emphasis. Tools such as Sweave [Leisch, 2002] began to allow statisticians to embed the code producing their analysis inside the text of the analysis report, so that a single command would run the analysis, produce the results, format tables and figures, and typeset the report for distribution. The goal at the time was to support Literate Statistical Practice, which “encourages the construction of documentation for data management and statistical analysis as the code for it is produced” [Rossini, 2001]; the design descended from the original literate programming system developed by Knuth [1984] for software, specialized to the needs of statistical software in particular.
Literate Statistical Practice had many practical advantages: it eliminated the tedium of copying and pasting results from statistical software packages into reports, for example, or of regenerating figures from scratch when small changes were needed. But the importance of these tools has grown dramatically as reproducibility has become a greater concern. Literate Statistical Practice is now seen as reproducible statistical practice, where all steps of the analysis are documented as precise code embedded directly into the same document used to generate the report describing the final results, and any interested reader of the report can easily examine and run the analysis for themselves. Tools like knitr and R Markdown [Xie, 2015, Xie et al., 2018] have made reproducible reports easier to write and easier to use, contributing to their rapid spread.
This is a dramatic advance in statistical practice, and one whose effects are only beginning to be seen. Bion et al. [2018], for example, describe the widespread adoption of knitr among the data science team at Airbnb, who use it routinely to share their analyses and business experiments with each other, with management, and even publicly as blog posts and academic publications. Baumer et al. [2014] described the use of knitr in introductory statistics courses, “to enable students to develop the basic capacity to undertake modern data analysis and communicate their results,” and ÇetinkayaRundel and Rundel [2018] described its coordinated use in an undergraduate course sequence designed to develop statistical computing skills in students from the introductory level onward.
Presciently, this use of literate statistical tools for education was advocated by Nolan and Temple Lang [2007] in a paper preceding the one that is the subject of this special issue. In the paper, they argued literate statistical tools could also be valuable for students learning how to analyze data independently for the first time. An instructor could prepare a document describing a dataset, showing how it can be loaded and processed, discussing various analysis approaches and their merits, and displaying the results of a full analysis—and along with the text describing these choices for the student, all the code would be available and embedded in the document, so the student could easily run the analysis or experiment with it to produce new results.
Along with the advance of tools to make literate reportwriting easier, other major innovations have come in tools to make manipulating, restructuring, summarizing, and aggregating data much easier. The notion of “tidy data” [Wickham, 2014], meaning that “each variable is a column, each observation is a row, and each type of observational unit is a table,” has been developed to describe the data format that is easiest to use for subsequent analysis. The R community has developed new packages that express core operations that can be applied to tidy data, to summarize, rearrange, or group it, making it easy for statisticians to express the operations they need to put their data in the most convenient form. These packages, part of the “tidyverse” [Wickham et al., 2019], represent an advance in the practicality of statistical computing and in the ease of teaching students core principles that generalize to many situations they may face in the workplace.
Not only, then, do statisticians need computational skills to use statistical packages to manipulate disparate data and obtain results, they need the skills to structure their analyses and reports in literate, reproducible form, so they can easily be communicated to decisionmakers and shared with students or statisticians who might want to explore or alter the analysis. Students might also use these same skills to implement and explore the statistical methods they learn in class. Much of the advance in undergraduate statistical computing in the past ten years has been in ensuring students gain the basic programming skills they need to do this.
2.2 Software Engineering
If our goal is to train statisticians and data scientists who can conduct flexible analyses, with all the freedom that a statistical programming language and its associated ecosystem of packages can provide, and who can communicate their results effectively, then literate statistical tools and tidy data methods clearly belong in our curricula. But we should not narrow our focus too quickly. Not every statistical task fits into the framework of “receive data and a question, conduct an analysis, and prepare a report on the results.” This is, in fact, a reflection of the success of statistics and data science: no longer relegated to roles as consultants or analysts brought in to answer specific questions, statisticians and data scientists increasingly hold roles as integral parts of teams developing products and delivering services.
Bion et al. [2018] provided an insightful example of this shift. At Airbnb, a service allowing property owners to list spaces for shortterm rentals, the data science team does several kinds of work. They might, for example, build “a machine learning algorithm that takes into account a variety of points of information, such as the date of the night to price, the listing’s location and amenities, and the listing’s booking histories” to suggest a fair price for a host to charge for guests to spend a night at their accommodations. They may also conduct experiments to determine fee pricing strategies that maximize revenue. But the outcome of this work is not simply a report to be submitted to management describing the results of their modeling efforts: after developing a model to predict revenues or select fees, and building “a wide variety of prototypes in R” to validate that such a model works well, they “worked with engineers to bring the prototype into production,” where hosts now use its recommendations every day. That is, the final outcome was the deployment of a piece of statistical software, which now continually operates as a part of Airbnb’s core business.
Similar scenarios can be routinely found at any company that uses statistical and data science methods and analysis as part of its business. Statisticians, rather than simply delivering analyses to managers, are called on to build systems that operate continuously: fraud detection systems, logistics optimization systems, product recommendation systems, and so on. The challenges and skills involved in building a longrunning system are quite different from those involved in writing a single analysis report. Reportwriting skills are still crucial—for example, as statisticians build prototypes for new systems and write reports analyzing their performance on real business data—but are one part of a larger set of computing skills.
We need not look only to industry to see statistical software used for purposes other than writing analytical reports. Consider, for example, a Ph.D. student in statistics conducting theoretical work on new models for some complex type of data. Many students in this position write many thousands of lines of code in the process of preparing their dissertation: code to simulate data with known parameters, code to estimate the model from data, code to run simulation studies that verify theoretical results, code to calculate diagnostics or measure goodness of fit, code to fit benchmark models and run comparisons… While some of this code fits into the form of a traditional analysis report—a simulation study can be presented as a report, for example—much of it does not. Much of the code forms a
product that an ambitious graduate student might release as an R package submitted to CRAN or a Python package on PyPI, allowing other researchers to benefit from their theoretical labor and use the newly developed methods for their own practical purposes.To build a successful product, the statistician must have several skills that a literate reportwriter need not possess. A widely used R or Python package must be modular and flexible so that users can adapt it to their purposes. It must be welltested so users are confident that its output is correct. The code must be wellorganized so that it can be maintained as users request new features or a Ph.D. thesis pivots to a new type of model. Changes must be tracked in a version control system so the author can collaborate with others who contribute code. Algorithms and data structures must be wellselected so that the new methods implemented in the package are fast on large datasets and can be run frequently as new data arrives. Most statisticians pick up these skills only accidentally, or by hardearned experience as they struggle with a tangled and unwieldy package that must be constantly tweaked and revised to serve the purposes of their project. Nonetheless, many statisticians do make the effort because releasing packages and products based on their work is a critical part of their job. This alone is a reason to train statisticians in the requisite skills.
But the impact of effective software engineering in statistics goes beyond that on individual statisticians. The spread of opensource statistical software, and the trend for authors of new methods to share packages implementing their methods, creates a bazaar of statistical tools that are free for the taking by any statistician or data scientist. These tools spread statistical best practices and techniques more effectively than any article or textbook, by making the use of good statistical methods as easy as loading a package. This is often cited as a factor in the R language’s success: since academic statisticians largely use R to implement new methods, and often share these implementations as R packages for others to use, R users in academia and industry quickly benefit from new research. Bion et al. [2018] cite examples from their own experience, noting that “Since R is used by so many leading statisticians and academics, the latest techniques are often made available very quickly in R, which is useful for data scientists in industry who wish to take advantage of new methods as they are developed.” And besides R packages, statisticians also produce other widely used tools: the Stan language for Bayesian modeling and MCMC [Carpenter et al., 2017], for example, has become influential, being widely used in academia and industry. The broader impact of this ecosystem is hard to overstate.
The shift in statistical computing is noticeable in the work done by statisticians in academia, but also by the jobs they take in industry. For example, of 91 total graduates of Carnegie Mellon University’s Bachelor’s in Statistics & Data Science program in 2018, 56 reported being employed at the time of a survey of their career outcomes, and of these, 14 (25%) reported a job title implying a software development role, such as “Software Engineer” [CMU Career & Professional Development Center, 2018]. It has been our experience that many industry roles titled “Data Scientist” or “Data Analyst” also heavily involve software development.
In the next section, we discuss what it would mean for a statistical computing curriculum to prepare students for these roles, and discuss a class we developed to do so. We emphasize the software engineering skills this course must teach, but just as importantly describe the pedagogical strategies we use to ensure that all students in the course get extensive practice in the most important skills of software engineering.
3 Course Content
A central feature of our Statistical Computing course curriculum is that it does not teach a programming language. This has two facets. First, we do not spend time in class teaching language syntax or basic programming constructs. Such material, if it is to be useful, tends to dominate the structure of a course and focus students’ attention on language tools and features, detracting from the deeper goals we want the course to achieve. Because it is a graduate course, we can assume that our students have already had some exposure to programming, either through an undergraduate course in statistical computing or at least through using R for data analysis. Many have had much more than that, but overall the students’ backgrounds are highly variable and certainly not of uniform depth. We have been able to accommodate this range by offering paths of varying sophistication and scaffolding in both homework (see Section 3.3), inclass activities, and the Challenge project (see Section 3.4).
Second and more importantly, the course is largely programminglanguage agnostic. Students are not required to use any specific programming language, and examples in our lectures are often given in R, Python, C++, Clojure, Racket, and other languages. As the concepts, practices, and skills covered in the course are widely relevant, this design decision makes the course accessible to students from a range of programming backgrounds. We believe an even bigger benefit of this approach is the perspective it offers. A focus on a single language tends to conflate approaches to problems with the way their solutions can be expressed in the target language. Instead, we often show example snippets in multiple languages so that students can see both the commonalities that are conserved across most languages as well as some contrast across other possible design choices and idioms. Students quickly find that, even excepting a few syntactic details, they can understand the approach taken in a wide range of languages and that this affects how they approach problems even in their chosen language.
Students frequently choose to submit their homework in Python or R, but we allow them to submit homework in any language that at least one of the course instructors or TAs is able to read, so students in past semesters have also used Ruby, Java, Julia, C++, JavaScript, Clojure and Haskell. We encourage students to get some experience in a new language, even if only on simple and familiar problems. We offer a range of homework problems so that students interested in trying a new programming language can gain familiarity with it and see how the new language differs from what they are used to. Students use this opportunity in different ways. For example, some explore complementary languages (e.g., R and Python or R and C++ for use with Rcpp) and others explore more widely, going deep in functional or strongly typed languages.
Similarly, the Statistical Computing course does not cover specific packages, such as the Tidyverse [Wickham et al., 2019] or tools to obtain and manipulate data (such as Web scraping systems). Students can (and often do) use such tools, and we provide individual guidance when needed outside class. It would perhaps be most accurate to say that the teaches a problemsolving philosophy, encompassed in the four themes presented below, rather than simply a collection of tools suited for specific problems.
Comprehensive lecture notes are available at the course website, https://36750.github.io/. (It should be noted that the website includes more than one semester worth of material: it includes notes on every topic that has been taught in the course, even as the selection of topics has changed from year to year.) In Section 3.2 below, we will describe the main themes covered by these notes and our lectures, and the following sections will then discuss the course pedagogy: the activities and assignments we use to reinforce these themes and ensure students leave the course prepared to engineer software.
3.1 Course Context and History
Our Statistical Computing course has been offered as a onesemester course each fall semester since Fall 2015, after being piloted as a halfsemester “mini” course in Spring 2015. Since Fall 2016, the course has been a required part of the core curriculum for two graduate programs in the Department of Statistics & Data Science at Carnegie Mellon University: the Master’s of Statistical Practice and the Ph.D. in Statistics & Data Science.
The two programs have somewhat different goals. The Master’s of Statistical Practice is a twosemester professional master’s program designed to train students to become professional statisticians, with an emphasis on professional skills so that graduates can effectively communicate statistical results to their managers or consulting clients. Graduates often pursue careers as data analysts or statistical consultants in fields including pharmaceutical research, finance and banking, and technology. The program was deliberately designed with pedagogical principles in mind; Greenhouse and Seltman [2018] described how the program breaks data analysis down into key component skills, gives students practice with each skill over the course of the curriculum, and then integrates these skills into a capstone consulting project during the spring semester. After steady growth since the program’s creation in Fall 2009, it now enrolls roughly 30–35 students each year.
The Ph.D. in Statistics & Data Science is the primary graduate research degree offered by Carnegie Mellon University’s Department of Statistics & Data Science. Roughly 10 students enter the program each year from widely varied educational backgrounds: while some were undergraduate mathematics or statistics majors, others have entered from computer science, physics, economics, and various other technical fields. The program emphasizes data analysis skills along with theoretical competence: every student completes an Advanced Data Analysis project during their second and third semesters, collaborating with an outside scientist to tackle a difficult data analysis problem. Some students continue to pursue applied and methodological work for their thesis after completing the ADA project, while others use the project to motivate advanced theoretical work.
Apart from Master’s and Ph.D. students, the Statistical Computing course often has several graduate students from other departments whose work involves large amounts of computing, as well as advanced undergraduate students in Statistics & Data Science who have extensive programming experience. In total, the class has an enrollment of around 40–50.
3.2 Four Themes
3.2.1 Effective programming practices
The requirements on statistical software are growing quickly in several dimensions: complexity (from short analysis scripts to fullfeatured packages and products), scale (from local data files to large and often distributed data sets), user base (from a practitioner and collaborators to a disparate audience from diverse fields), and lifecycle (from the analyses for a paper to a continuouslymaintained package or system). This theme focuses on developing practices that make software more reliable, more usable, and easier to maintain. The goal is to help students master the skills needed to build complex software easily. Such practices include testing, debugging, profiling, version control, code review, clear naming, and effective documentation.
Unit testing, for example, is a practice often adopted in professional software development to ensure code is correct and defects are not accidentally introduce. A unit test isolates a specific “unit” of code, such as a function or class, and runs that unit with specific inputs, then verifies that the unit’s output matches the expected output. Unit tests are written as code themselves, usually using a package designed to organize test cases, run all tests automatically with a single command, and report summary results indicating which test cases failed and giving descriptions of the failure. The testthat package is widely used for R, for example [Wickham, 2011]. Similar packages are available for almost every common programming language.
Unit testing is an essential part of software engineering for several reasons. Most obviously, it helps ensure correctness of software: if each function or method has detailed test cases, and these test cases can easily be checked every time the code is changed, mistakes can be detected immediately. Software engineering research shows that while writing unit tests takes extra time, this time can in some settings be made up in the time saved fixing problems and debugging errors [Williams et al., 2003, Bissi et al., 2016]. There have been several notable cases of errors in statistical and scientific software going undetected for years, even as the software was used routinely for scientific research, underscoring the importance of effective testing [Eklund et al., 2016].
Less obviously, unit testing helps authors plan and structure their code. Software engineering best practices usually recommend writing unit tests for code before writing the code, and this ensures that the programmer must carefully consider the inputs, outputs, and correct behavior of their code before writing the code itself. A better understanding of the goals of the code usually leads to better code. Also, it is easier to unit test short simple functions than long complicated ones, since it is easier to think of test cases for simple functions than complex ones. Dividing up complex tasks into simple pieces so they can be easily tested also encourages software to be composed of small, easily understood pieces, which is a key software design recommendation (see Section 3.2.2).
Students in Statistical Computing are expected to write unit tests for every homework assignment they complete, and several of the inclass programming activities discussed in Section 4.1 ask students what kinds of unit tests they would write for a particular algorithm or programming task. Additionally, an automated system runs the unit tests submitted with each homework assignment and verifies that all tests pass, serving the dual purpose of verifying that students submit code that is portable and runs successfully on computers other than their own.
As another example, code review has become an essential practice in the workplace. In collaborative software projects, such as software developed by a team in a large company or an opensource package developed by a group of volunteers, collaborators often practice peer code review [Rigby and Bird, 2013, Sadowski et al., 2018]. Each proposed change to the software, such as a new feature or fix for a problem, is submitted for review by a coworker or collaborator. The peer gives linebyline feedback on the code, enforcing project style guidelines, looking for flawed reasoning and bugs, and giving feedback so the code can be improved. Only after the proposed change has passed peer review is it merged into the product or package.
Experiments have shown that code review detects bugs and improves software quality, often by encouraging code to be clearer and easier to maintain [Mäntylä and Lassenius, 2009, Beller et al., 2014], and it is often a mandatory part of the software development process at software companies. Popular software collaboration platforms like GitHub and GitLab support code review through “pull requests” or “merge requests,” which display the new version of the code alongside the old with changes highlighted. Collaborators can leave comments on any line of code, and authors can reply to these comments and hold entire conversations about the changes using the collaboration platform.
We give students experience with code review in two ways. We first host an inclass activity in which students reviewed real code written (by a course instructor) to solve a specific problem. Students are given a code review checklist to follow, encouraging them to look at specific features of the code and comment on them as part of their review. Later in the semester, students conduct peer code review of their Challenge projects (see Section 3.4): students are matched into pairs, and during the 80minute class period, the pairs review each other’s code on GitHub, leaving code review comments in the same way teaching assistants would.
3.2.2 Fundamental principles of software design
Throughout the semester, we emphasize a few key principles of design. This includes modularity and code organization, the way that the many features required of software are organized into files, functions, classes, scripts, and so on. It also includes how information is shared across different parts of a system and the structure of interfaces the software makes available to access its features. Effective software design is a powerful means to manage complexity. In a poor design, functions may become large and complicated, and interact with each other in complicated ways, so that changing one small part of the code’s behavior requires intricate surgery on many separate functions. In an effective design, functions are small and modular, and features are clearly separated so that changing behavior only requires changing a few specific functions that are clearly responsible for that behavior. Good design also facilitates code reuse and refinement.
This kind of design is not a major concern when writing a literate statistical report, where apart from a few helper functions, the main organization is logical: each chunk of code produces results corresponding to that section of the report, or produces data to be used by later chunks. But when developing a software package that’s intended to be reusable, careful design is essential: a good design makes it easy to modify and extend the package, for example as a Ph.D. student explores new methods in a thesis, while a bad design can make changes excruciatingly difficult.
The semesterlong Challenge project, described below in Section 3.4, is designed to give students practical design experience. Since the project requires students to build a complicated product over the entire semester, and later portions of the project require students to build on or modify earlier portions, they either experience the benefits of welldesigned code or suffer the pain of modifying poorly designed code.
3.2.3 Important algorithms, data structures, and representations
In recent years, a large amount of statistical research has been focused on scaling statistical methods: building methods that can be practically applied to enormous datasets without an extravagantly large computational budget. Commonly, statistical computing courses prepare students to work with large datasets by teaching them different tools. SQL database systems, for example, are designed to efficiently query massive datasets that do not fit in memory, while software like Hadoop and Apache Spark are designed to distribute calculations across multiple servers that each have their own chunk of data. Students might also learn to use tools like Rcpp, which allows users to write the most computationally intensive parts of their R packages in efficient C++ code that can be easily called from within R [Eddelbuettel and Francois, 2011]. (Cython [Behnel et al., 2011] serves a similar role in the Python world.) And students are often exposed to R programming folk wisdom: use builtin functions whenever possible, avoid for
loops in favor of vectorization, and perhaps use packages like
data.table instead of native data frames.But this misses the ways that careful software design can make code efficient and scalable. First, the designer must select an algorithm appropriate to the task at hand, meaning the designer must be familiar with general strategies for designing algorithms. For example, the divideandconquer strategy is to reduce a large problem into several smaller problems whose solutions can be combined to yield the overall solution; by doing this recursively, a complex problem can be reduced to many small and trivial problems. The divideandconquer strategy is widely used in computer science to produce algorithms that scale well to large datasets (for example, mergesort is a divideandconquer sorting algorithm), and it has been recently explored as a tool for implementing statistical methods on large datasets [Jordan, 2013]. Dynamic programming is another widely used strategy to break problems into smaller problems whose solutions can be combined efficiently; for example, the fused lasso can be expressed as a dynamic programming problem, leading to a lineartime algorithm [Johnson, 2013].
Along with the appropriate algorithm, the designer must also select appropriate data structures to store the data needed for an algorithm in an efficient way. Students used to working in R for data analysis tend to think of data frames, matrices, and vectors as the only available data structures, and often write algorithms that require repeatedly scanning through an entire dataset to find relevant elements—which often scales poorly to large datasets. But data structures like hash tables (dictionaries), binary trees, stacks, and queues all have their uses in statistical algorithms, and storing data in the appropriate data structure can make the difference between repeatedly having to scan an entire dataset and being able to quickly extract only the relevant portion.
In statistics, for example, the d tree can store data points, each in dimensions, and can find all data points in specific intervals or ranges in time, rather than requiring a loop through all data points [Bentley, 1975]. This can also be used to solve
nearestneighbor problems efficiently, and has been adapted to perform fast approximate kernel density estimation
[Gray and Moore, 2003]. Other tree data structures are widely used by SQL databases to efficiently process queries with complex WHERE clauses.The Statistical Computing course covers basic algorithmic strategies such as divideandconquer and dynamic programming, as well as basic data structures. We emphasize to students that selecting the appropriate algorithm and data structure can be much more important than the ordinary R performance tips: an algorithm that uses repeated (but vectorized) scans through an entire data frame or vector is intrinsically less efficient than one that uses a d tree to do the same operation in time, for example, even if the tree operations are written in unsophisticated R. Hash tables can store a collection of values labeled with keys, and look up an item by key in time, meaning that even as the hash table becomes large, access times do not change.
In the course, various homework assignments pose simple problems that can be solved in an obvious but tremendously inefficient way as well as a lessobvious but efficient way using an appropriate algorithm and data structure. (These can be challenging in R, which does not provide efficient data structures by default; for example, looking up an item by name in an R list requires an scan through all entries, and base R does not provide flexible collection data structures [Barry, 2018].) Along with the Challenge projects, these assignments teach students that fast code often requires careful thinking about the organization and use of data.
3.2.4 Essential tools and methods
There are many ways to produce good statistical software, but there are several core tools that are almost universally useful. This theme focuses on giving students substantive experience with those tools to give them a foundation for building good practices and habits going forward. Such tools include editors/integrated development environments, version control systems, debuggers and profilers, databases (relational and otherwise), and the command line.
We build experience with such tools into the structure of the course, providing support for a range of quality tools while giving students as much flexibility as possible. For instance, we cover using SQL in class and let students interact with SQL databases through their favorite programming language in assignments and class activities. But we also give them hands on experience with a specific system (PostgreSQL) for concreteness. Similarly, while it is possible to work completely through graphical user interfaces (GUIs), we believe that command line tools can add value for practioners and be a powerful tool in many circumstances. We show students how to use these tools and build a set of practices for the effective design and use of such tools.
Version control is a more challenging example. It is a critical tool for successfully developing largescale software in collaboration with others, allowing team members to track the history of every source file. It allows changes to be systematically recorded and reverted if necessary, and allows collaborators working independently to make changes to code without interfering with each other’s work. Version control software is now widely used by companies that develop software, as well as by collaborative opensource software projects. The R system itself, for example, is developed using the Subversion version control system.
There are many version control systems available, with nontrivial differences in use and details. Because concreteness is needed and complexity is high, we focus particularly on Git, which is perhaps the most widely used modern version control system, particularly with the growth of webbased collaboration tools such as GitHub, GitLab, and Bitbucket that enhance Git with online tools for filing bug reports, reviewing proposed changes to code, and tracking project timelines and milestones. Students who are familiar with Git will be better prepared to work at companies that use Git or similar systems, or to collaborate on any of the thousands of open source data science packages that organize their development with Git. Bryan [2018] has also persuasively argued that Git is also valuable for managing the data, code, and figures involved in a literate statistical analysis, such as data analysis reports, further enhancing their reproducibility by making the history of changes visible. Moreover, students who become fluent with Git will have little difficulty transferring their skills to another system.
Unfortunately, Git is not known for being userfriendly. Its primary interface is through the command line shell, and it relies on a vocabulary of terms (like “working tree,” “index,” “HEAD,” “blob,” and even “treeish”) that make its documentation almost impenetrable to new users. We have found that simply teaching these concepts to students in a lecture is not sufficient; students need extensive practice using Git throughout the semester to begin to grasp its concepts. Hence students use Git and GitHub to submit all homework assignments and course projects, starting with an inclass tutorial during the first week.
Nonetheless, Git has proven to be one of the main stumbling blocks for students in the course, and we spend substantial time in office hours helping to correct accidental merge conflicts, commits accidentally made on incorrect branches, and so on. We expect this to be an area of further development of the course, as we experiment with ways to teach this crucial tool more effectively.
3.3 Homework Problem Bank
Because our students have varied levels of programming experience and have varied goals for the course, we felt that an ordinary homework assignment structure, where each student completes the same assignments, would be inadequate. Some students may already be familiar with certain topics and require little practice, while others may be most interested in a specific topics they expect to use in their research or future job and desire specific practice for that topic. We want to support varied paths through the material, where each student can be challenged and build their skills throughout.
To facilitate this, we developed a problem bank containing over 70 programming problems (and growing), categorized by topic and difficulty level. Some problems have direct connections to statistics, while others simply illustrate programming principles. Example assignments include:

Implement a kernel smoothing procedure, but allow the user to pass in any kernel function and metric of their choice, not limited by any prespecified list of functions built in to the code.

Use graph search algorithms to solve mazes.

Implement simple text tokenization to produce bagofwords vectors for documents, then explore different distance metrics between documents of different types.
Each assignment required students to write code, but also to write unit tests verifying their implementation. Some assignments were specifically designed to prepare students for the Challenge projects by providing small problems to practice skills that would be needed at a larger scale in the Challenge.
Throughout the semester, assignments from the problem bank are posted as the relevant topics were covered. Students can select from all the posted assignments those they believe are most interesting or relevant to their needs, and complete the assignments at their own pace. Our grading system (see Section 4.2) simply requires students to satisfactorily complete a certain number of assignments by the end of the semester.
3.4 Challenge Project
The homework problem bank allowed students to gain practice in many topics covered in the course, but small homework assignments do not cover a key learning goal of the course: learning effective strategies to design and develop largescale software—with all the complexity it entails—over the course of months or years. This skill would be required by Ph.D. students writing software for their dissertation or by graduates in industry working on large projects and products, and we felt it important to give students authentic practice deploying the skills taught in the class.
To achieve this, the course includes a semesterlong Challenge Project. Students can choose between several Challenges on varying topics, though each project is designed to cover a wide range of skills. For example, in the Fall 2019 semester, students could choose between four Challenges:
 AnomalySpeed

Students implement the isolation forest method for anomaly detection
[Liu et al., 2012], and apply the method to videos to detect local anomalies in video frames. By applying the anomaly detection to videos of cars driving down a road, students process the detected anomalies, count the number of cars and calculate each car’s speed.  Autocomplete Me

Based on an assignment by Wayne [2016], this Challenge requires students to build a data structure and functions that support autocomplete features: given a database of tens of thousands of potential queries, and the first few letters of text typed by a user, the autocomplete system should quickly and efficiently suggest the highestweighted matching queries. Students learn to construct an efficient data structure (a trie) and use an efficient traversal algorithm to find only the highestweighted queries first (using priority queues). After building this, they convert their code into a reusable package, then use profiling to guide their use of Rcpp, Cython, or a similar system to make the autocomplete system even faster.
 Classification Trees

Students implement an algorithm to build classification trees, then use this code to build random forest classifiers
[Breiman, 2001]. They then extend this code to build classification trees using data stored in a SQL database without loading this data into memory, in principle allowing the construction of trees for very large datasets.  Shazam

The Shazam smartphone app can automatically match a short snippet of audio to a database of recorded music, allowing users to answer questions like “what’s the name of this song?” Students use the audio fingerprinting methods described by Lichun Wang [2003], storing their fingerprints in a SQL database, and implement a simple version of Shazam to match audio snippets to a database of recordings.
The Challenge projects were scaffolded by being broken into four parts, due regularly throughout the semester. In the first part, students were asked to consider the design of their code but not to actually implement it: they wrote function signatures but left the bodies of the functions empty. The only functioning code to be submitted was extensive unit tests to demonstrate what the code should do, once it was written. This encouraged students to think more deeply about their design before plunging in to implementation. The subsequent parts asked students to successively add features to their project, following the requirements given in the assignment.
4 Pedagogy
The design of the Statistical Computing course cannot be separated from its pedagogy. In a course intended to teach students a complex skill, such as engineering statistical software, the only way for students to learn the skill—and not just the prerequisite knowledge for that skill—is regular practice with targeted feedback. Regular practice gives students the opportunity to practice the skills we teach, while targeted feedback ensures they learn those skills and learn from their mistakes. A course design that ensures students gain regular practice and feedback is hence more important than any specific lecture topic.
4.1 Active Learning
Inclass active learning has been repeatedly shown to improve student learning in a variety of STEM fields
[Freeman et al., 2014]. Most of our course lectures incorporate active learning activities in various forms. For example, early in the semester we cover unit testing and how to test that code is correct; we have found that students often struggle to think of example test cases to use to verify code they write, so a large portion of the unittesting class is spent having the students work in small groups to think of test cases for a few example functions.Our experience has been that much of the student learning in a lecture seems to come from these activities. We frequently discover that after spending 30 minutes lecturing on a particular topic and feeling that the lecture is going well, an inclass activity reveals that some students may still be deeply confused and have misinterpreted much of the lecture. Without these activities, the confusion could only be detected (and corrected) much later.
For key concepts that all students must master to succeed in the course, we have gradually shifted from large lecture components to large inclass activities that students can later turn in for homework credit, ensuring that all students practice the necessary skills before completing other assignments or the Challenge project. While our course does not have a formal lab component conducted in a computer lab, we suspect a lab component would have similar benefits.
4.2 SpecificationBased Grading and Revision
During the initial iteration of the course, homework grading was fairly conventional: students turned in their code, and the teaching assistant graded the submissions using a simple rubric, assigning a point value to each rubric category. However, we quickly found this system to be ineffective. It was clear that students were not reviewing the feedback or using it to improve their future submissions.
Beginning in Fall 2016, we switched to a new system. Students select assignments from the problem bank (Section 3.3), submit their code through GitHub as pull requests, and the teaching assistants leave comments on the pull requests using GitHub’s code review features. Pull requests are a common convention for proposing changes to software: the pull request lists changes that the author proposes be integrated (“pulled”) into the software, for a colleague or supervisor to review. Platforms like GitHub add collaboration and review features so that reviewers can leave comments on specific lines of code, propose specific changes, and indicate whether they approve of the changes overall. Our teaching assistants use these features to give detailed linebyline feedback on submitted code.
Crucially, rather than a numerical grade, there are only two possible outcomes: the assignment can be marked Mastered, indicating the student has successfully solved the problem, has used appropriate algorithms and data structures, and has written the code with good style and with unit tests that verified its correctness; or, if those criteria are not met, the assignment is marked “Not Yet Mastered” and the student is asked to revise it according to the TA’s feedback. Students receive course credit only for Mastered assignments. Prior research on mastery learning systems suggests strategies like this can improve student learning [Kulik et al., 1990], though the additional flexibility in our system distinguishes it from the more widely used mastery grading systems.
At the same time as we introduced this grading system, we also cut the required number of homework assignments for the course roughly in half. Our goal was to hold assignments to a high standard and expect that a large fraction of assignments would be revised, so while students would complete fewer assignments, they would spend more time on each assignment and eventually complete work of much higher quality. It was now impossible for students to ignore feedback from the TAs. Moreover, the focus on revision gives students practice with a constellation of skills that are often neglected in instruction.
Challenge projects use a similar system. Each part of the Challenge is submitted to the teaching assistants for review as it is completed, and the student must make satisfactory revisions before they can submit the next part of the Challenge. Once all parts of the Challenge are complete and meet the requirements, it can be graded either Mastered or Sophisticated. Sophisticated submissions are those that demonstrate exceptional software engineering skill, by being welldesigned, clearly written, thoroughly tested, and unusually flexible and modular; earning a Sophisticated grade on the Challenge increases the student’s course grade, as discussed below in Section 4.3.
4.3 Grading System
The course structure poses challenges for assigning final course grades. As described in Section 3.3, students select homework assignments from a bank of possible problems. Students can complete assignments in any order, and there are no fixed deadlines for submitting individual assignments. Each submitted assignment is either Mastered or requires revision, so there are no points to be averaged to give a final grade.
This places the focus of the course firmly on mastery of the concepts, rather than on meeting arbitrary deadlines and logistical requirements, but does pose a problem for assigning final grades. We base grades on the number of Mastered assignments: a simple table in the course syllabus specifies how many assignments must be Mastered to achieve a certain grade. The Challenge project is required, but achieving a Sophisticated on the Challenge can also move the final course grade up one grade level.
To account for the fact that individual homework assignments may involve different degrees of difficulty, we assigned each homework assignment a certain number of “points.” Typical assignments were 2 points, but difficult assignments were 3 points and trivial assignments 1 point. The only possible outcome is still either Mastered, meaning the student receives all the points, or revision, meaning the student does not yet receive any points for the assignment. There is no partial credit for submissions. The grade table is then based on the number of points Mastered, rather than the number of assignments, and accounts for assignment difficulty, preventing students from simply choosing the easiest assignments to complete.
We found that this grading system has several advantages. It is noticeably simpler than a normal pointsbased system to grade and administer, reducing workload on the TAs. It also reduces uncertainty for students: the students know that if they revise their submissions as instructed, they will be given a certain number of points, and they know exactly how those points translate into grades. There is no concern over a final exam that heavily affects final grades—there is no final exam—and students know exactly how much work they must do for a certain grade.
5 Conclusion
The trends that motivated Nolan and Temple Lang’s call for a new focus on computing in statistics curricula have only accelerated. The scope and complexity of computing tasks expected of statisticians and data scientists require not only a detailed knowledge of statistical methods and numerical approaches but also skills related to data management, collaboration, and software engineering. Our Statistical Computing course is designed to give students a firm foundation—and authentic practice—in these skills. It is intended to serve as a base on which their programming experience can be built throughout their graduate career, and beyond. Novel features of our course include emphasis on the practice of software design, our multipath problem bank, our grading system, integrated code review, regular revision, and a languageagnostic approach. The course has been successful in both our Ph.D. and Master’s program. Implementation, particularly at scale, is a continuing challenge, and we will continue to develop and refine the course.
The skills we emphasize have changed from year to year as our understanding of student needs have changed, and the prior skills of each student cohort have varied significantly from year to year. Also, the unconventional course structure, while giving students significant freedom to explore their interests, has required a great deal of experimentation to improve, and likely will continue to change each year as we learn what structure best teaches our intended skills.
Several challenges remain to be solved. Git has proven to be a major obstacle to students; they must use it to submit each homework assignment on GitHub for review by the TAs, but students who make mistakes often attempt to fix them with solutions found online, leading to tangled Git histories that must be carefully untangled by the instructors or TAs before assignments can be graded. The flexible homework system can sometimes be too flexible, and without formal deadlines, students can procrastinate and get into difficult situations, or skip assignments selected as inclass activities and miss important skills needed for the Challenge project. The demands on course TAs to give highquality feedback on assignments while also holding regular office hours can be stressful, requiring skilled TAs and large time commitments. We are working to streamline the course, automate some aspects of homework submission and review, and improve the student experience.
While a fair portion of the material we emphasize (including algorithms, data structures, testing, software design, and wideranging assignments) might seem more naturally obtained from a Computer Science department, we have found many reasons to prefer that material within a Statistics curriculum. First, we explicitly address these themes, with significant class time; these skills tend to be threaded more implicitly throughout a typical computer science curriculum. Second, we can focus the practice of our target skills with context and examples that are meaningful to Statistics and Data Science students. Third, having a single foundation course early in the Statistics graduate curriculum has been a significant downstream productivity enhancer for our students, who soon use the skills in their other courses and projects. Finally, we know of no other course, in Computer Science or elsewhere, that achieves our target balance on our themes and skill development.
We also see literate reportwriting as a valuable part of statistical practice; we certainly are not advocating for that to be replaced. Indeed, we feel that the themes we emphasize here provide a useful complement to the skills needed for reportwriting, and students in our graduate programs receive repeated practice in data analysis and writing in their other courses. Our undergraduate programs also extensively integrate reportwriting, in R Markdown, throughout several courses.
Statistical computing is a broad topic, and students come with varied backgrounds and downstream needs. There are many reasonable approaches to teaching students the computing skills they will need in their careers. We believe, however, that working statisticians in industry and academia face increasingly stringent demands on the capabilities, usability, and maintenance of the software they produce, and that literate reportwriting is only one component of the many computational skills a successful statistician will need. As the field’s computing curricula continue to evolve, we believe that this reality needs to be faced head on.
Acknowledgments
We thank Peter Freeman for contributions to the course design and content during its first incarnation. We are indebted to our excellent teaching assistants for their outstanding work in the class: Philipp Burckhardt, Niccolò Dalmasso, Sangwon Hyun, Nicolás Kim, Francis Kovacs, Taylor Pospisil, and Shamindra Shrotriya. Jerzy Wieczorek gave us feedback on our mastery grading system.
References
 Barry [2018] Timothy Barry. Collections in R: Review and proposal. The R Journal, 10(1):455–471, 2018. doi: 10.32614/RJ2018037.
 Baumer et al. [2014] Benjamin S. Baumer, Mine ÇetinkayaRundel, Andrew Bray, Linda Loi, and Nicholas J. Horton. R Markdown: Integrating a reproducible analysis tool into introductory statistics. Technology Innovations in Statistics Education, 8(1), 2014. URL https://escholarship.org/uc/item/90b2f5xh.
 Behnel et al. [2011] S. Behnel, R. Bradshaw, C. Citro, L. Dalcin, D.S. Seljebotn, and K. Smith. Cython: The best of both worlds. Computing in Science Engineering, 13(2):31 –39, 2011. ISSN 15219615. doi: 10.1109/MCSE.2010.118.
 Beller et al. [2014] Moritz Beller, Alberto Bacchelli, Andy Zaidman, and Elmar Juergens. Modern code reviews in opensource projects: Which problems do they fix? In Proceedings of the 11th Working Conference on Mining Software Repositories, pages 202–211, 2014. doi: 10.1145/2597073.2597082.
 Bentley [1975] Jon Louis Bentley. Multidimensional binary search trees used for associative searching. Communications of the ACM, 18(9):509–517, 1975. doi: 10.1145/361002.361007.
 Bion et al. [2018] Ricardo Bion, Robert Chang, and Jason Goodman. How R helps Airbnb make the most of its data. The American Statistician, 72(1):46–52, 2018. doi: 10.1080/00031305.2017.1392362.
 Bissi et al. [2016] Wilson Bissi, Adolfo Gustavo Serra Seca Neto, and Maria Claudia Figueiredo Pereira Emer. The effects of test driven development on internal quality, external quality and productivity: A systematic review. Information and Software Technology, 74:45–54, 2016. doi: 10.1016/j.infsof.2016.02.004.
 Breiman [2001] Leo Breiman. Random forests. Machine Learning, 45(1):5–32, 2001. doi: 10.1023/A:1010933404324.

Bryan [2018]
Jennifer Bryan.
Excuse me, do you have a moment to talk about version control?
The American Statistician, 72(1):20–27, 2018. doi: 10.1080/00031305.2017.1399928.  Carpenter et al. [2017] Bob Carpenter, Andrew Gelman, Matthew Hoffman, Daniel Lee, Ben Goodrich, Michael Betancourt, Marcus Brubaker, Jiqiang Guo, Peter Li, and Allen Riddell. Stan: A probabilistic programming language. Journal of Statistical Software, 76(1), 2017. ISSN 15487660. doi: 10.18637/jss.v076.i01.
 ÇetinkayaRundel and Rundel [2018] Mine ÇetinkayaRundel and Colin Rundel. Infrastructure and tools for teaching computing throughout the statistical curriculum. The American Statistician, 72(1):58–65, 2018. doi: 10.1080/00031305.2017.1397549.
 CMU Career & Professional Development Center [2018] CMU Career & Professional Development Center. First destination outcomes: Dietrich College Statistics & Data Science, bachelor’s, 2018. URL https://www.cmu.edu/career/documents/2018_one_pagers/dc/Bachelors%20Stats.pdf.
 Eddelbuettel and Francois [2011] Dirk Eddelbuettel and Romain Francois. Rcpp: Seamless R and C++ integration. Journal of Statistical Software, 40(8), 2011. doi: 10.18637/jss.v040.i08.
 Eklund et al. [2016] Anders Eklund, Thomas E Nichols, and Hans Knutsson. Cluster failure: Why fMRI inferences for spatial extent have inflated falsepositive rates. Proceedings of the National Academy of Sciences, 113(28):7900–7905, 2016. doi: 10.1073/pnas.1602413113.
 Freeman et al. [2014] S. Freeman, S. L. Eddy, M. McDonough, M. K. Smith, N. Okoroafor, H. Jordt, and M. P. Wenderoth. Active learning increases student performance in science, engineering, and mathematics. Proceedings of the National Academy of Sciences, 111(23):8410–8415, May 2014. doi: 10.1073/pnas.1319030111.
 Gray and Moore [2003] Alexander G. Gray and Andrew W. Moore. Nonparametric density estimation: Toward computational tractability. In Proceedings of the 2003 SIAM International Conference on Data Mining, pages 203–211, 2003. doi: 10.1137/1.9781611972733.19.
 Greenhouse and Seltman [2018] Joel B Greenhouse and Howard J Seltman. On teaching statistical practice: From novice to expert. The American Statistician, 72(2):147–154, 2018. doi: 10.1080/00031305.2016.1270230.
 Johnson [2013] Nicholas A Johnson. A dynamic programming algorithm for the fused lasso and segmentation. Journal of Computational and Graphical Statistics, 22(2):246–260, 2013. doi: 10.1080/10618600.2012.681238.
 Jordan [2013] Michael I Jordan. On statistics, computation and scalability. Bernoulli, 19(4):1378–1390, 2013. doi: 10.3150/12BEJSP17.
 Knuth [1984] Donald E Knuth. Literate programming. The Computer Journal, 27(2):97–111, 1984. doi: 10.1093/comjnl/27.2.97.
 Kulik et al. [1990] ChenLin C Kulik, James A Kulik, and Robert L BangertDrowns. Effectiveness of mastery learning programs: A metaanalysis. Review of Educational Research, 60(2):265–299, 1990. doi: 10.3102/00346543060002265.
 Leisch [2002] Friedrich Leisch. Sweave: Dynamic generation of statistical reports using literate data analysis. In Wolfgang Härdle and Bernd Rönz, editors, Compstat 2002 — Proceedings in Computational Statistics, pages 575–580. Physica Verlag, Heidelberg, 2002. ISBN 3790815179.
 Lichun Wang [2003] Avery Lichun Wang. An industrialstrength audio search algorithm. In Proceedings of the 4th International Conference on Music Information Retrieval, 2003.
 Liu et al. [2012] Fei Tony Liu, Kai Ming Ting, and ZhiHua Zhou. Isolationbased anomaly detection. ACM Transactions on Knowledge Discovery from Data, 6(1):3:1–3:39, March 2012. doi: 10.1145/2133360.2133363.
 Mäntylä and Lassenius [2009] M. V. Mäntylä and C. Lassenius. What types of defects are really discovered in code reviews? IEEE Transactions on Software Engineering, 35(3):430–448, May 2009. ISSN 23263881. doi: 10.1109/TSE.2008.71.
 Nolan and Temple Lang [2007] Deborah Nolan and Duncan Temple Lang. Dynamic, interactive documents for teaching statistical practice. International Statistical Review, 75(3):295–321, Dec 2007. doi: 10.1111/j.17515823.2007.00025.x.
 Nolan and Temple Lang [2010] Deborah Nolan and Duncan Temple Lang. Computing in the statistics curricula. The American Statistician, 64(2):97–107, 2010. doi: 10.1198/tast.2010.09132.
 R Core Team [2019] R Core Team. R: A Language and Environment for Statistical Computing. R Foundation for Statistical Computing, Vienna, Austria, 2019. URL https://www.Rproject.org/.
 Rigby and Bird [2013] Peter C Rigby and Christian Bird. Convergent contemporary software peer review practices. In Proceedings of the 9th Joint Meeting on Foundations of Software Engineering, pages 202–212, 2013. doi: 10.1145/2491411.2491444.
 Rossini [2001] A J Rossini. Literate statistical practice. In K Hornik and F Leisch, editors, Proceedings of the 2nd InternationalWorkshop on Distributed Statistical Computing, 2001.
 Sadowski et al. [2018] Caitlin Sadowski, Emma Söderberg, Luke Church, Michal Sipko, and Alberto Bacchelli. Modern code review: A case study at Google. In Proceedings of the 40th International Conference on Software Engineering: Software Engineering in Practice, pages 181–190, 2018. doi: 10.1145/3183519.3183525.
 Wayne [2016] Kevin Wayne. Autocompleteme. In SIGCSE Nifty Assignments, 2016. URL http://nifty.stanford.edu/2016/wayneautocompleteme/.
 Wickham [2011] Hadley Wickham. testthat: Get started with testing. The R Journal, 3(1):5–10, 2011. URL https://journal.rproject.org/archive/20111/RJournal_20111_Wickham.pdf.
 Wickham [2014] Hadley Wickham. Tidy data. Journal of Statistical Software, 59(10), 2014. doi: 10.18637/jss.v059.i10.
 Wickham et al. [2019] Hadley Wickham, Mara Averick, Jennifer Bryan, Winston Chang, Lucy D’Agostino McGowan, Romain François, Garrett Grolemund, Alex Hayes, Lionel Henry, Jim Hester, Max Kuhn, Thomas Lin Pedersen, Evan Miller, Stephan Milton Bache, Kirill Müller, Jeroen Ooms, David Robinson, Dana Paige Seidel, Vitalie Spinu, Kohske Takahashi, Davis Vaughan, Claus Wilke, Kara Woo, and Hiroaki Yutani. Welcome to the tidyverse. Journal of Open Source Software, 4(43):1686, Nov 2019. doi: 10.21105/joss.01686.
 Williams et al. [2003] L. Williams, E. M. Maximilien, and M. Vouk. Testdriven development as a defectreduction practice. In 14th International Symposium on Software Reliability Engineering, pages 34–45, Nov 2003. doi: 10.1109/ISSRE.2003.1251029.
 Xie [2015] Yihui Xie. Dynamic Documents with R and knitr. Chapman and Hall/CRC, Boca Raton, Florida, 2nd edition, 2015. URL https://yihui.name/knitr/. ISBN 9781498716963.
 Xie et al. [2018] Yihui Xie, J.J. Allaire, and Garrett Grolemund. R Markdown: The Definitive Guide. Chapman and Hall/CRC, Boca Raton, Florida, 2018. URL https://bookdown.org/yihui/rmarkdown. ISBN 9781138359338.
Comments
There are no comments yet.