├── .Rbuildignore ├── .github ├── .gitignore ├── move.yml └── workflows │ └── quarto.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE.md ├── README.md ├── _output.yml ├── _quarto.yml ├── documentation.qmd ├── errors.qmd ├── files.qmd ├── functions.qmd ├── ga_script.html ├── ggplot2.qmd ├── git.qmd ├── index.qmd ├── news.qmd ├── package-files.qmd ├── pipes.qmd ├── plausible.html ├── style.Rproj ├── styler-addin.png ├── styles.scss ├── syntax.qmd └── tests.qmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^\.travis\.yml$ 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | ^\.github$ 5 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/move.yml: -------------------------------------------------------------------------------- 1 | # Configuration for move-issues bot - https://github.com/dessant/move-issues 2 | 3 | # Delete the command comment when it contains no other content 4 | deleteCommand: true 5 | 6 | # Close the source issue after moving 7 | closeSourceIssue: true 8 | 9 | # Lock the source issue after moving 10 | lockSourceIssue: false 11 | 12 | # Mention issue and comment authors 13 | mentionAuthors: true 14 | 15 | # Preserve mentions in the issue content 16 | keepContentMentions: true 17 | 18 | # Set custom aliases for targets 19 | # aliases: 20 | # r: repo 21 | # or: owner/repo 22 | -------------------------------------------------------------------------------- /.github/workflows/quarto.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main, master] 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | name: quarto.yml 8 | 9 | jobs: 10 | quarto: 11 | runs-on: ubuntu-latest 12 | env: 13 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: r-lib/actions/setup-r@v2 18 | with: 19 | use-public-rspm: true 20 | 21 | - uses: r-lib/actions/setup-r-dependencies@v2 22 | 23 | - name: Install Quarto 24 | uses: quarto-dev/quarto-actions/install-quarto@v1 25 | 26 | - name: Render book 27 | run: quarto render 28 | 29 | - name: Deploy to GitHub pages 🚀 30 | if: github.event_name != 'pull_request' 31 | uses: JamesIves/github-pages-deploy-action@4.1.4 32 | with: 33 | branch: gh-pages 34 | folder: _book 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | _main.* 6 | _book 7 | 8 | /.quarto/ 9 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: tidyversestyle 2 | Title: Tidyverse style 3 | Version: 1.0.0 4 | Authors@R: c( 5 | person("Hadley", "Wickham", , "hadley@rstudio.com", c("aut", "cre")) 6 | ) 7 | URL: https://github.com/hadley/adv-r 8 | Depends: 9 | R (>= 3.1.0) 10 | Imports: 11 | bookdown, 12 | rmarkdown, 13 | png, 14 | bslib, 15 | downlit, 16 | xml2 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Creative Commons Legal Code 2 | 3 | ## Attribution-ShareAlike 3.0 Unported 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. 6 | 7 | ### License 8 | 9 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 10 | 11 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 12 | 13 | #### 1. Definitions 14 | 15 | - **a.** **"Adaptation"** means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. 16 | 17 | - **b.** **"Collection"** means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. 18 | 19 | - **c.** **"Creative Commons Compatible License"** means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. 20 | 21 | - **d.** **"Distribute"** means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. 22 | 23 | - **e.** **"License Elements"** means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. 24 | 25 | - **f.** **"Licensor"** means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. 26 | 27 | - **g.** **"Original Author"** means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. 28 | 29 | - **h.** **"Work"** means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. 30 | 31 | - **i.** **"You"** means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. 32 | 33 | - **j.** **"Publicly Perform"** means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. 34 | 35 | - **k.** **"Reproduce"** means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 36 | 37 | #### 2. Fair Dealing Rights 38 | 39 | Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 40 | 41 | #### 3. License Grant 42 | 43 | Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: 44 | 45 | - **a.** to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; 46 | 47 | - **b.** to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; 48 | 49 | - **c.** to Distribute and Publicly Perform the Work including as incorporated in Collections; and, 50 | 51 | - **d.** to Distribute and Publicly Perform Adaptations. 52 | 53 | - **e.** For the avoidance of doubt: 54 | 55 | - **i.** **Non-waivable Compulsory License Schemes.** In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; 56 | 57 | - **ii.** **Waivable Compulsory License Schemes.** In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, 58 | 59 | - **iii.** **Voluntary License Schemes.** The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. 60 | 61 | The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 62 | 63 | #### 4. Fair Dealing Rights 64 | 65 | The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: 66 | 67 | - **a.** You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. 68 | 69 | - **b.** You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. 70 | 71 | - **c.** If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 6(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. 72 | 73 | - **d.** Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 74 | 75 | #### 5. Representations, Warranties and Disclaimer 76 | 77 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 78 | 79 | #### 6. Limitation on Liability 80 | 81 | EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 82 | 83 | #### 7. Termination 84 | 85 | - **a.** This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. 86 | 87 | - **b.** Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 88 | 89 | #### 8. Miscellaneous 90 | 91 | - **a.** Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. 92 | 93 | - **b.** Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. 94 | 95 | - **c.** If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 96 | 97 | - **d.** No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. 98 | 99 | - **e.** This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. 100 | 101 | - **f.** The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. 102 | 103 | ### Creative Commons Notice 104 | 105 | Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. 106 | 107 | Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. 108 | 109 | Creative Commons may be contacted at . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![bookdown](https://github.com/tidyverse/style/actions/workflows/bookdown.yaml/badge.svg)](https://github.com/tidyverse/style/actions/workflows/bookdown.yaml) 3 | 4 | 5 | An R Style Guide written in **bookdown** (https://github.com/rstudio/bookdown). 6 | -------------------------------------------------------------------------------- /_output.yml: -------------------------------------------------------------------------------- 1 | bookdown::bs4_book: 2 | includes: 3 | in_header: [ga_script.html] 4 | repo: https://github.com/tidyverse/style 5 | 6 | bookdown::pdf_book: 7 | latex_engine: xelatex 8 | citation_package: natbib 9 | keep_tex: yes 10 | 11 | bookdown::epub_book: default -------------------------------------------------------------------------------- /_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: book 3 | 4 | book: 5 | title: Tidyverse style guide 6 | author: The tidyverse team 7 | 8 | site-url: https://style.tidyverse.org 9 | repo-url: https://github.com/tidyverse/style/ 10 | repo-branch: main 11 | repo-actions: [edit, issue] 12 | 13 | chapters: 14 | - index.qmd 15 | 16 | - part: Analyses 17 | chapters: 18 | - files.qmd 19 | - syntax.qmd 20 | - functions.qmd 21 | - pipes.qmd 22 | - ggplot2.qmd 23 | 24 | - part: Packages 25 | chapters: 26 | - package-files.qmd 27 | - documentation.qmd 28 | - tests.qmd 29 | - errors.qmd 30 | - news.qmd 31 | 32 | - part: Other 33 | chapters: 34 | - git.qmd 35 | 36 | format: 37 | html: 38 | include-in-header: "plausible.html" 39 | callout-appearance: simple 40 | theme: 41 | - styles.scss 42 | 43 | knitr: 44 | opts_chunk: 45 | eval: false 46 | -------------------------------------------------------------------------------- /documentation.qmd: -------------------------------------------------------------------------------- 1 | # Documentation {#sec-documentation} 2 | 3 | ## Introduction 4 | 5 | Documentation of code is essential, even if the only person using your code 6 | is future-you. Use [roxygen2](https://github.com/klutometis/roxygen) with 7 | [markdown](https://github.com/klutometis/roxygen/blob/master/vignettes/markdown.Rmd) 8 | support enabled to keep your documentation close to the code. 9 | 10 | ## Title and description 11 | 12 | Use the first line of your function documentation to provide a concise title that describes the function, dataset, or class. Titles should use sentence case 13 | but not end with a full stop (`.`). 14 | 15 | ```{r} 16 | #' Combine values into a vector or list 17 | #' 18 | #' This is a generic function which combines its arguments. 19 | #' 20 | ``` 21 | 22 | There is no need to use the explicit `@title` or `@description` tags, except 23 | in the case of the description if it is multiple paragraphs or includes 24 | more complex formatting like a bulleted list. 25 | 26 | ```{r} 27 | #' Apply a function to each element of a vector 28 | #' 29 | #' @description 30 | #' The map function transform the input, returning a vector the same length 31 | #' as the input. 32 | #' 33 | #' * `map()` returns a list or a data frame 34 | #' * `map_lgl()`, `map_int()`, `map_dbl()` and `map_chr()` return 35 | #' vectors of the corresponding type (or die trying); 36 | #' * `map_dfr()` and `map_dfc()` return data frames created by row-binding 37 | #' and column-binding respectively. They require dplyr to be installed. 38 | ``` 39 | 40 | ## Indents and line breaks 41 | 42 | Always indent with one space after `#'`. If any description corresponding to a 43 | `roxygen` tag spans over multiple lines, add another two spaces of extra 44 | indention. 45 | 46 | ```{r} 47 | #' @param key The bare (unquoted) name of the column whose values will be used 48 | #' as column headings. 49 | ``` 50 | 51 | Alternatively, tags that span over multiple lines (like `@description`, `@examples` and `@section`) can have the corresponding tag on its own line and then subsequent lines don't need to be indented. 52 | 53 | ```{r} 54 | #' @examples 55 | #' 1 + 1 56 | #' sin(pi) 57 | ``` 58 | 59 | Use line breaks before/after sections where needed: 60 | 61 | ```{r} 62 | #' @section Tidy data: 63 | #' When applied to a data frame, row names are silently dropped. To preserve, 64 | #' convert to an explicit variable with [tibble::rownames_to_column()]. 65 | #' 66 | #' @section Scoped filtering: 67 | #' The three [scoped] variants ([filter_all()], [filter_if()] and 68 | #' [filter_at()]) make it easy to apply a filtering condition to a 69 | #' selection of variables. 70 | ``` 71 | 72 | ## Documenting parameters 73 | 74 | For most tags, like `@param`, `@seealso` and `@return`, the text should be a 75 | sentence, starting with a capital letter and ending with a full stop. 76 | 77 | ```{r} 78 | #' @param key The bare (unquoted) name of the column whose values will be used 79 | #' as column headings. 80 | ``` 81 | 82 | If some functions share parameters, you can use `@inheritParams` to avoid 83 | duplication of content in multiple places. 84 | 85 | ```{r} 86 | #' @inheritParams function_to_inherit_from 87 | ``` 88 | 89 | ## Capitalization and full stops 90 | 91 | For all bullets, enumerations, argument descriptions and the like, use sentence 92 | case and put a period at the end of each text element, even if it is only a few 93 | words. However, avoid capitalization of function names or packages since R is 94 | case sensitive. Use a colon before enumerations or bulleted lists. 95 | 96 | ```{r} 97 | #' @details 98 | #' In the following, we present the bullets of the list: 99 | #' * Four cats are few animals. 100 | #' * forcats is a package. 101 | ``` 102 | 103 | ## Cross-linking 104 | 105 | Cross-referencing is encouraged, both within R's help file system as well as to 106 | external resources. 107 | 108 | List closely related functions in `@seealso`. A single related function can be written as a sentence: 109 | 110 | ```{r} 111 | #' @seealso [fct_lump()] to automatically convert the rarest (or most common) 112 | #' levels to "other". 113 | ``` 114 | 115 | More recommendations should be organised in a bulleted list: 116 | 117 | ```{r} 118 | #' @seealso 119 | #' * [tibble()] constructs from individual columns. 120 | #' * [enframe()] converts a named vector into a two-column tibble (names and 121 | #' values). 122 | #' * [name-repair] documents the details of name repair. 123 | ``` 124 | 125 | 126 | You can link to functions in other packages by using the fully qualified function name, i.e. `[pkg::function()]`. 127 | 128 | If you have a family of related functions, you can use the `@family` tag to 129 | automatically add appropriate lists and interlinks to the `@seealso` section. 130 | Family names are plural. In dplyr, the verbs `arrange()`, `filter()`, 131 | `mutate()`, `slice()`, `summarize()` form the family of single table verbs. 132 | 133 | ```{r} 134 | #' @family single table verbs 135 | ``` 136 | 137 | When linking to external resources either include the full url inline with `<>`, or the surrounding prose and link text should make it extremely clear where the hyperlink goes. Avoid text like ["click here"](https://www.smashingmagazine.com/2012/06/links-should-never-say-click-here/). 138 | 139 | ## R code 140 | 141 | Text that contains valid R code should be marked as such using backticks. This includes: 142 | 143 | * Function arguments, e.g. `na.rm`. 144 | * Values, e.g. `TRUE`, `FALSE`, `NA`, `NaN`, `...`, `NULL` 145 | * Literal R code, e.g. `mean(x, na.rm = TRUE)` 146 | * Class names, e.g. "a tibble will have class `tbl_df` ..." 147 | 148 | You can use code syntax for function names, like `` `tibble()` ``, but consider if it would be better to use a cross-link instead, like `[tibble()]`. If you refer to a function multiple times in one topic, you only need to link the first reference. 149 | 150 | ## Package names 151 | 152 | Don't use code font for package names. If the package name might be misinterpreted as an ordinary word, disambiguate by following it with "package" or by wrapping the package name in `{}` (but not both). 153 | 154 | ``` 155 | # Good 156 | Use the glue package to flexibly interpolate values into strings. 157 | Use {glue} to flexibly interpolate values into strings. 158 | 159 | # Bad 160 | Use glue to flexibly interpolate values into strings. 161 | Use `glue` to flexibly interpolate values into strings. 162 | Use the {glue} package to flexibly interpolate values into strings. 163 | ``` 164 | 165 | If a package name comes at the start of a sentence, treat it like a proper name and don't capitalize it: 166 | 167 | ``` 168 | # Good 169 | dplyr provides a grammar of data manipulation, providing a consistent set of verbs that help you solve the most common data manipulation challenges. 170 | 171 | # Bad 172 | Dplyr provides a grammar of data manipulation, providing a consistent set of verbs that help you solve the most common data manipulation challenges 173 | ``` 174 | 175 | ## Internal functions 176 | 177 | Internal functions should be documented with `#'` comments as per usual. 178 | Use the `@noRd` tag to prevent `.Rd` files from being generated. 179 | 180 | ```{r} 181 | #' Drop last 182 | #' 183 | #' Drops the last element from a vector. 184 | #' 185 | #' @param x A vector object to be trimmed. 186 | #' 187 | #' @noRd 188 | ``` 189 | -------------------------------------------------------------------------------- /errors.qmd: -------------------------------------------------------------------------------- 1 | # Error messages 2 | 3 | An error message should start with a general statement of the problem then give a concise description of what went wrong. 4 | Consistent use of punctuation and formatting makes errors easier to parse. 5 | 6 | This guide assumes that you're using `cli::cli_abort()`. 7 | We are transitioning to use this function in the tidyverse because it: 8 | 9 | - Makes it easy to generate bulleted lists. 10 | - Uses glue style interpolation to insert data into the error. 11 | - Supports a wide range of [inline markup](https://cli.r-lib.org/reference/inline-markup.html). 12 | - Provides convenient tools to [chain errors together](https://rlang.r-lib.org/reference/topic-error-chaining.html). 13 | - Can control the [name of the function](https://rlang.r-lib.org/reference/topic-error-call.html) shown in the error. 14 | 15 | Much of the advice in this guide still applies if you're using `stop()`, but it will be be much more work to generate the message. 16 | 17 | 18 | 19 | ## Problem statement 20 | 21 | Every error message should start with a general statement of the problem. 22 | It should be concise, but informative (This is hard!). 23 | The problem statement should use sentence case and end with a full stop. 24 | 25 | - If the cause of the problem is clear (e.g. an incorrect type or size), use "**must**": 26 | 27 | ```{r} 28 | dplyr::nth(1:10, "x") 29 | #> Error: 30 | #> ! `n` must be a numeric vector, not a character vector. 31 | 32 | dplyr::nth(1:10, 1:2) 33 | #> Error: 34 | #> ! `n` must have length 1, not length 2. 35 | ``` 36 | 37 | Do your best to tell the user both what is expected ("a numeric vector") and what they actually provided ("a character vector"). 38 | 39 | - If you cannot state what was expected, use "**can't**": 40 | 41 | ```{r} 42 | mtcars |> pull(b) 43 | #> Error: 44 | #> ! Can't find column `b` in `.data`. 45 | 46 | as_vector(environment()) 47 | #> Error: 48 | #> ! Can't coerce `.x` to a vector. 49 | 50 | purrr::modify_depth(list(list(x = 1)), 3, ~ . + 1) 51 | #> Error: 52 | #> ! Can't find specified `.depth` in `.x`. 53 | ``` 54 | 55 | 56 | 57 | ## Error location 58 | 59 | Ideally the error message should mention the failing function call. 60 | 61 | 62 | 63 | See for more about how to pass calls through error helpers. 64 | 65 | ## Error details 66 | 67 | After the problem statement, use a bulleted list to provide further information. 68 | Use cross bullets (`x`) to let the user know what the problem is, then use info bullets (`i`) to provide contextual information. 69 | These are easy to create with `cli_abort()`: 70 | 71 | 72 | 73 | Try to keep the sentences short and sweet: 74 | 75 | ```{r} 76 | # Good 77 | vec_slice(letters, 100) 78 | #> ! Can't subset elements past the end. 79 | #> ℹ Location 100 doesn't exist. 80 | #> ℹ There are only 26 elements. 81 | 82 | # Bad 83 | vec_slice(letters, 100) 84 | #> ! Must index an existing element. 85 | #> There are 26 elements and you've tried to subset element 100. 86 | ``` 87 | 88 | Do your best to reveal the location, name, and/or content of the troublesome component of the input. 89 | The goal is to make it as easy as possible for the user to find and fix the problem. 90 | 91 | ```{r} 92 | # Good 93 | map_int(1:5, ~ "x") 94 | #> Error: 95 | #> ! Each result must be a single integer. 96 | #> ✖ Result 1 is a character vector. 97 | 98 | # Bad 99 | map_int(1:5, ~ "x") 100 | #> Error: 101 | #> ! Each result must be a single integer 102 | ``` 103 | 104 | (It is often not easy to identify the exact problem; it may require passing around extra arguments so that error messages generated at a lower-level can know the original source. For frequently used functions, the effort is typically worth it.) 105 | 106 | If the source of the error is unclear, avoid pointing the user in the wrong direction by giving an opinion about the source of the error: 107 | 108 | ```{r} 109 | # Good 110 | pull(mtcars, b) 111 | #> Error: 112 | #> ! Can't find column `b` in `.data`. 113 | 114 | tibble(x = 1:2, y = 1:3, z = 1) 115 | #> Error: 116 | #> ! Tibble columns must have compatible sizes. 117 | #> • Size 2: Existing data. 118 | #> • Size 3: Column `y`. 119 | #> ℹ Only values of size one are recycled. 120 | 121 | # Bad: implies one argument at fault 122 | pull(mtcars, b) 123 | #> Error: 124 | #> ! Column `b` must exist in `.data`. 125 | 126 | pull(mtcars, b) 127 | #> Error: 128 | #> ! `.data` must contain column `b`. 129 | 130 | tibble(x = 1:2, y = 1:3, z = 1) 131 | #> Error: 132 | #> ! Column `x` must be length 1 or 3, not 2. 133 | ``` 134 | 135 | If there are multiple issues, or an inconsistency revealed across several arguments or items, prefer a bulleted list: 136 | 137 | ```{r} 138 | # Good 139 | purrr::reduce2(1:4, 1:2, `+`) 140 | #> Error: 141 | #> ! `.x` and `.y` must have compatible lengths: 142 | #> ✖ `.x` has length 4 143 | #> ✖ `.y` has length 2 144 | 145 | # Bad: harder to scan 146 | purrr::reduce2(1:4, 1:2, `+`) 147 | #> Error: 148 | #> ! `.x` and `.y` must have compatible lengths: `.x` has length 4 and 149 | #> `.y` has length 2 150 | ``` 151 | 152 | If the list of issues might be long, make sure to truncate to only show the first few: 153 | 154 | ```{r} 155 | # Good 156 | #> Error: NAs found at 1,000,000 locations: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ... 157 | ``` 158 | 159 | If you want to correctly pluralise the error message, consider using `ngettext()`. 160 | See the notes in `?ngettext()` for some challenges related to correct translation to other languages. 161 | 162 | ## Hints 163 | 164 | If the source of the error is clear and common, you may want to provide a hint as to how to fix it. 165 | The hint should be the last bullet, use an info bullet (`i`), and end in a question mark. 166 | 167 | ```{r} 168 | dplyr::filter(iris, Species = "setosa") 169 | #> Error: 170 | #> ! Filter specifications must be named. 171 | #> ℹ Did you mean `Species == "setosa"`? 172 | 173 | ggplot2::ggplot(ggplot2::aes()) 174 | #> Error: 175 | #> ! Can't plot data with class "uneval". 176 | #> ℹ Did you accidentally provide the results of aes() to the `data` argument? 177 | 178 | mtcars |> ggplot() |> geom_point() 179 | #> Error in `validate_mapping()`: 180 | #> ! `mapping` must be created by `aes()`. 181 | #> ℹ Did you use `%>%` or `|>` instead of `+`? 182 | ``` 183 | 184 | Hints are particularly important if the source of the error is far away from the root cause: 185 | 186 | ```{r} 187 | # Bad 188 | mean[[1]] 189 | #> Error: 190 | #> ! object of type 'closure' is not subsettable 191 | 192 | # BETTER 193 | mean[[1]] 194 | #> Error: 195 | #> ! Can't subset a function. 196 | 197 | # BEST 198 | mean[[1]] 199 | #> Error: 200 | #> ! Can't subset a function. 201 | #> ℹ Have you forgotten to define a variable named `mean`? 202 | ``` 203 | 204 | Good hints are difficult to write because you want to avoid steering users in the wrong direction. 205 | Generally, we avoid writing a hint unless the problem is common, and you can easily find a common pattern of incorrect usage (e.g. by searching StackOverflow). 206 | 207 | ## Punctuation 208 | 209 | 210 | 211 | - Errors should be written in sentence case, and should end in a full stop. 212 | Bullets should be formatted similarly; make sure to capitalise the first word (unless it's an argument or column name). 213 | 214 | - Prefer the singular in problem statements: 215 | 216 | ```{r} 217 | # Good 218 | map_int(1:2, ~ "a") 219 | #> Error: 220 | #> ! Each result must be coercible to a single integer. 221 | #> ✖ Result 1 is a character vector. 222 | 223 | # Bad 224 | map_int(1:2, ~ "a") 225 | #> Error: 226 | #> ! Results must be coercible to single integers. 227 | #> ✖ Result 1 is a character vector. 228 | ``` 229 | 230 | - If you can detect multiple problems, list up to five. 231 | This allows the user to fix multiple problems in a single pass without being overwhelmed by many errors that may have the same source. 232 | 233 | 234 | 235 | ```{r} 236 | # BETTER 237 | map_int(1:10, ~ "a") 238 | #> Error: 239 | #> ! Each result must be coercible to a single integer. 240 | #> ✖ Result 1 is a character vector 241 | #> ✖ Result 2 is a character vector 242 | #> ✖ Result 3 is a character vector 243 | #> ✖ Result 4 is a character vector 244 | #> ✖ Result 5 is a character vector 245 | #> ... and 5 more problems 246 | ``` 247 | 248 | - Pick a natural connector between problem statement and error location: this may be ", not", ";", or ":" depending on the context. 249 | 250 | - Surround the names of arguments in backticks, e.g. `` `x` ``. 251 | Use "column" to disambiguate columns and arguments: `` Column `x` ``. 252 | Avoid "variable", because it is ambiguous. 253 | 254 | 255 | - Ideally, each component of the error message should be less than 80 characters wide. 256 | Do not add manual line breaks to long error messages; they will not look correct if the console is narrower (or much wider) than expected. 257 | Instead, use bullets to break up the error into shorter logical components. 258 | In case you do need longer sentences, let cli perform paragraph wrapping automatically. 259 | It inserts newlines automatically depending on the width of the console. 260 | 261 | ## Before and after 262 | 263 | More examples gathered from around the tidyverse. 264 | 265 | ```{r} 266 | dplyr::filter(mtcars, cyl) 267 | #> BEFORE: 268 | #> ! Argument 2 filter condition does not evaluate to a logical vector. 269 | 270 | #> AFTER: 271 | #> ! Each argument must be a logical vector. 272 | #> * Argument 2 (`cyl`) is an integer vector. 273 | 274 | tibble::tribble("x", "y") 275 | #> BEFORE: ! Expected at least one column name; e.g. `~name` 276 | #> AFTER: ! Must supply at least one column name, e.g. `~name`. 277 | 278 | ggplot2::ggplot(data = diamonds) + ggplot2::geom_line(ggplot2::aes(x = cut)) 279 | #> BEFORE: ! geom_line requires the following missing aesthetics: y 280 | #> AFTER: ! `geom_line()` must have the following aesthetics: `y`. 281 | 282 | dplyr::rename(mtcars, cyl = xxx) 283 | #> BEFORE: ! `xxx` contains unknown variables 284 | #> AFTER: ! Can't find column `xxx` in `.data`. 285 | 286 | dplyr::arrange(mtcars, xxx) 287 | #> BEFORE: ! Evaluation error: object 'xxx' not found. 288 | #> AFTER: ! Can't find column `xxx` in `.data`. 289 | ``` 290 | 291 | ## Localisation 292 | 293 | It is encouraged to be as informative as possible, but each sentence should be very simple to make localisation and translation possible. 294 | [A Localization Horror Story: It Could Happen To You](https://metacpan.org/pod/distribution/Locale-Maketext/lib/Locale/Maketext/TPJ13.pod) is a Good summary of the challenges of localising error messages. 295 | You might not support localised messages right now but you should make it as easy as possible to do it in the future. 296 | -------------------------------------------------------------------------------- /files.qmd: -------------------------------------------------------------------------------- 1 | # Files {#sec-files} 2 | 3 | ## Names 4 | 5 | 1. File names should be **machine readable**: avoid spaces, symbols, and special characters. Prefer file names that are all lower case, and never 6 | have names that differ only in their capitalization. Delimit words with `-` or `_`. Use `.R` as the extension of R files. 7 | 8 | ``` 9 | # Good 10 | fit_models.R 11 | utility_functions.R 12 | exploratory-data-analysis.R 13 | 14 | # Bad 15 | fit models.R 16 | foo.r 17 | ExploratoryDataAnaylsis.r 18 | ``` 19 | 20 | 2. File names should be **human readable**: use file names to describe what's in the file. 21 | 22 | ``` 23 | # good 24 | report-draft-notes.txt 25 | 26 | # bad 27 | temp.r 28 | ``` 29 | 30 | Use the same structure for closely related files: 31 | 32 | ``` 33 | # good 34 | fig-eda.png 35 | fig-model-3.png 36 | 37 | # bad 38 | figure eda.PNG 39 | fig model three.png 40 | ``` 41 | 42 | 3. File names should play well with default ordering. If your file names contain dates, use yyyy-mm-dd (ISO8601) format so they sort in chronological order. If your file names include numbers, make sure to pad them with the appropriate number of zeros so that (e.g.) 11 doesn't get sorted before 2. If files should be used in a specific order, put the number at the start, not the end. 43 | 44 | ``` 45 | # good 46 | 01-load-data.R 47 | 02-exploratory-analysis.R 48 | 03-model-approach-1.R 49 | 04-model-approach-2.R 50 | 2025-01-01-report.Rmd 51 | 2025-02-01.report.Rmd 52 | 53 | # bad 54 | alternative model.R 55 | code for exploratory analysis.r 56 | feb 01 report.Rmd 57 | jan 01 report.Rmd 58 | model_first_try.R 59 | run-first.r 60 | ``` 61 | 62 | If you later realise that you've missed some steps, it's tempting to use `02a`, 63 | `02b`, etc. However, I think it's generally better to bite the bullet and 64 | rename all files. 65 | 66 | 4. Don't [tempt fate](https://phdcomics.com/comics.php?f=1531) by using "final" or similar words in file names. Instead either rely on Git to track changes over time, or failing that, put the date in the file name. 67 | 68 | ``` 69 | # good 70 | report-2022-03-20.qmd 71 | report-2022-04-02.qmd 72 | 73 | # bad 74 | finalreport.qmd 75 | FinalReport-2.qmd 76 | ``` 77 | 78 | ## Organisation 79 | 80 | It's hard to describe exactly how you should organise your code across multiple files. I think the best rule of thumb is that if you can give a file a concise name that still evokes its contents, you've arrived at a good organisation. But getting to that point is hard. 81 | 82 | ## Internal structure 83 | 84 | Use commented lines of `-` and `=` to break up your file into easily readable 85 | chunks. 86 | 87 | ```{r} 88 | # Load data --------------------------- 89 | 90 | # Plot data --------------------------- 91 | ``` 92 | 93 | If your script uses add-on packages, load them all at once at the very 94 | beginning of the file. This is more transparent than sprinkling `library()` 95 | calls throughout your code or having hidden dependencies that are loaded in a 96 | startup file, such as `.Rprofile`. 97 | -------------------------------------------------------------------------------- /functions.qmd: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | ## Naming 4 | 5 | As well as following the general advice for object names in @sec-objectnames, strive to use verbs for function names: 6 | 7 | ```{r} 8 | # Good 9 | add_row() 10 | permute() 11 | 12 | # Bad 13 | row_adder() 14 | permutation() 15 | ``` 16 | 17 | ## Anonymous functions 18 | 19 | Use the new lambda syntax: `\(x) x + 1` when writing short anonymous functions (i.e. when you define a function in an argument without giving it an explicit name). 20 | 21 | ```R 22 | # Good 23 | map(xs, \(x) mean((x + 5)^2)) 24 | map(xs, function(x) mean((x + 5)^2)) 25 | 26 | # Bad 27 | map(xs, ~ mean((.x + 5)^2)) 28 | ``` 29 | 30 | Don't use `\()` for multi-line functions: 31 | 32 | ```R 33 | # Good 34 | map(xs, function(x) { 35 | mean((x + 5)^2) 36 | }) 37 | 38 | # Bad 39 | map(xs, \(x) { 40 | mean((x + 5)^2) 41 | }) 42 | ``` 43 | 44 | Or when creating named functions: 45 | 46 | ```R 47 | # Good 48 | cv <- function(x) { 49 | sd(x) / mean(x) 50 | } 51 | 52 | # Bad 53 | cv <- \(x) sd(x) / mean(x) 54 | ``` 55 | 56 | Avoid using `\()` in a pipe, and remember to use informative argument names. 57 | 58 | ## Multi-line function definitions 59 | 60 | There are two options if the function name and definition can't fit on a single line. In both cases, each argument goes on its own line; the difference is how deep you indent it and where you put `)` and `{`: 61 | 62 | * **Single-indent**: indent the argument name with a single indent (i.e. two spaces). 63 | The trailing `)` and leading `{` go on a new line. 64 | 65 | ```{r} 66 | # Good 67 | long_function_name <- function( 68 | a = "a long argument", 69 | b = "another argument", 70 | c = "another long argument" 71 | ) { 72 | # As usual code is indented by two spaces. 73 | } 74 | ``` 75 | 76 | * **Hanging-indent**: indent the argument name to match the opening `(` of `function`. 77 | The trailing `)` and leading `{` go on the same line as the last argument. 78 | 79 | ```{r} 80 | # Good 81 | long_function_name <- function(a = "a long argument", 82 | b = "another argument", 83 | c = "another long argument") { 84 | # As usual code is indented by two spaces. 85 | } 86 | ``` 87 | 88 | These styles are designed to clearly separate the function definition from its body. 89 | 90 | ```{r} 91 | # Bad 92 | long_function_name <- function(a = "a long argument", 93 | b = "another argument", 94 | c = "another long argument") { 95 | # Here it's hard to spot where the definition ends and the 96 | # code begins, and to see all three function arguments 97 | } 98 | ``` 99 | 100 | If a function argument can't fit on a single line, this is a sign you should rework the argument to keep it [short and sweet](https://design.tidyverse.org/defaults-short-and-sweet.html). 101 | 102 | ## S7 103 | 104 | In S7, the method definition can be long because the function name is replaced by a method call that specifies the generic and dispatch classes. In this case we recommend the single-indent style. 105 | 106 | ```{r} 107 | method(from_provider, list(openai_provider, class_any)) <- function( 108 | provider, 109 | x, 110 | ..., 111 | error_call = caller_env() 112 | ) { 113 | ... 114 | } 115 | ``` 116 | 117 | If the method definition is too long to fit on one line, use the usual rules to 118 | spread the method arguments across multiple lines: 119 | 120 | ```{r} 121 | method( 122 | from_provider, 123 | list(openai_provider, class_any, a_very_long_class_name) 124 | ) <- function( 125 | provider, 126 | x, 127 | ..., 128 | error_call = caller_env() 129 | ) { 130 | ... 131 | } 132 | ``` 133 | 134 | ## `return()` 135 | 136 | Only use `return()` for early returns. Otherwise, rely on R to return the result 137 | of the last evaluated expression. 138 | 139 | ```{r} 140 | # Good 141 | find_abs <- function(x) { 142 | if (x > 0) { 143 | return(x) 144 | } 145 | x * -1 146 | } 147 | add_two <- function(x, y) { 148 | x + y 149 | } 150 | 151 | # Bad 152 | add_two <- function(x, y) { 153 | return(x + y) 154 | } 155 | ``` 156 | 157 | Return statements should always be on their own line because they have important effects on the control flow. See also [control flow modifiers](#control-flow-modifiers). 158 | 159 | ```{r} 160 | # Good 161 | find_abs <- function(x) { 162 | if (x > 0) { 163 | return(x) 164 | } 165 | x * -1 166 | } 167 | 168 | # Bad 169 | find_abs <- function(x) { 170 | if (x > 0) return(x) 171 | x * -1 172 | } 173 | ``` 174 | 175 | If your function is called primarily for its side-effects (like printing, 176 | plotting, or saving to disk), it should return the first argument invisibly. 177 | This makes it possible to use the function as part of a pipe. `print` methods 178 | should usually do this, like this example from [httr](http://httr.r-lib.org/): 179 | 180 | ```{r} 181 | print.url <- function(x, ...) { 182 | cat("Url: ", build_url(x), "\n", sep = "") 183 | invisible(x) 184 | } 185 | ``` 186 | 187 | ## Comments 188 | 189 | In code, use comments to explain the "why" not the "what" or "how". Each line 190 | of a comment should begin with the comment symbol and a single space: `# `. 191 | 192 | ```{r} 193 | # Good 194 | 195 | # Objects like data frames are treated as leaves 196 | x <- map_if(x, is_bare_list, recurse) 197 | 198 | 199 | # Bad 200 | 201 | # Recurse only with bare lists 202 | x <- map_if(x, is_bare_list, recurse) 203 | ``` 204 | 205 | Comments should be in sentence case, and only end with a full stop if they 206 | contain at least two sentences: 207 | 208 | ```{r} 209 | # Good 210 | 211 | # Objects like data frames are treated as leaves 212 | x <- map_if(x, is_bare_list, recurse) 213 | 214 | # Do not use `is.list()`. Objects like data frames must be treated 215 | # as leaves. 216 | x <- map_if(x, is_bare_list, recurse) 217 | 218 | 219 | # Bad 220 | 221 | # objects like data frames are treated as leaves 222 | x <- map_if(x, is_bare_list, recurse) 223 | 224 | # Objects like data frames are treated as leaves. 225 | x <- map_if(x, is_bare_list, recurse) 226 | ``` 227 | -------------------------------------------------------------------------------- /ga_script.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /ggplot2.qmd: -------------------------------------------------------------------------------- 1 | # ggplot2 2 | 3 | 4 | ## Introduction 5 | 6 | Styling suggestions for `+` used to separate ggplot2 layers are very similar to those for `|>` in pipelines. 7 | 8 | 9 | ## Whitespace 10 | 11 | `+` should always have a space before it, and should be followed by a new line. This is true even if your plot has only two layers. After the first step, each line should be indented by two spaces. 12 | 13 | If you are creating a ggplot off of a dplyr pipeline, there should only be one level of indentation. 14 | 15 | ```{r} 16 | # Good 17 | iris |> 18 | filter(Species == "setosa") |> 19 | ggplot(aes(x = Sepal.Width, y = Sepal.Length)) + 20 | geom_point() 21 | 22 | # Bad 23 | iris |> 24 | filter(Species == "setosa") |> 25 | ggplot(aes(x = Sepal.Width, y = Sepal.Length)) + 26 | geom_point() 27 | 28 | # Bad 29 | iris |> 30 | filter(Species == "setosa") |> 31 | ggplot(aes(x = Sepal.Width, y = Sepal.Length)) + geom_point() 32 | ``` 33 | 34 | 35 | ## Long lines 36 | 37 | If the arguments to a ggplot2 layer don't all fit on one line, put each argument on its own line and indent: 38 | 39 | ```{r} 40 | # Good 41 | iris |> 42 | ggplot(aes(x = Sepal.Width, y = Sepal.Length, color = Species)) + 43 | geom_point() + 44 | labs( 45 | x = "Sepal width, in cm", 46 | y = "Sepal length, in cm", 47 | title = "Sepal length vs. width of irises" 48 | ) 49 | 50 | # Bad 51 | iris |> 52 | ggplot(aes(x = Sepal.Width, y = Sepal.Length, color = Species)) + 53 | geom_point() + 54 | labs(x = "Sepal width, in cm", y = "Sepal length, in cm", title = "Sepal length vs. width of irises") 55 | ``` 56 | 57 | ggplot2 allows you to do data manipulation, such as filtering or slicing, within the `data` argument. Avoid this, and instead do the data manipulation in a pipeline before starting plotting. 58 | 59 | ```{r} 60 | # Good 61 | iris |> 62 | filter(Species == "setosa") |> 63 | ggplot(aes(x = Sepal.Width, y = Sepal.Length)) + 64 | geom_point() 65 | 66 | # Bad 67 | ggplot(filter(iris, Species == "setosa"), aes(x = Sepal.Width, y = Sepal.Length)) + 68 | geom_point() 69 | ``` 70 | -------------------------------------------------------------------------------- /git.qmd: -------------------------------------------------------------------------------- 1 | # Git/GitHub 2 | 3 | ## Commit messages 4 | 5 | Follow [standard git commit message advice](https://chris.beams.io/posts/git-commit/). In brief: 6 | 7 | * The first line is the subject, and should summarise the changes in the commit 8 | in under 50 characters. 9 | Use sentence case, but no period at the end. 10 | 11 | * If additional details are required, add a blank line, and then provide 12 | explanation and context in paragraph format. 13 | 14 | * If the commit fixes a GitHub issue include `Fixes #`. 15 | This will ensure that the issue is automatically closed when the commit 16 | is merged into main. 17 | 18 | ## Pull requests 19 | 20 | The title of a pull request should briefly describe the changes made. The title should be standalone and should not include the related issue number (i.e. don't write `Fixes #10`). 21 | 22 | For very simple changes, you can leave the description blank as there's no need to describe what will be obvious from looking at the diff. For more complex changes, you should give an overview of the changes. If the PR fixes an issue, make sure to include `Fixes #` in the description. 23 | -------------------------------------------------------------------------------- /index.qmd: -------------------------------------------------------------------------------- 1 | # Welcome {-} 2 | 3 | Good coding style is like correct punctuation: you can manage without it, butitsuremakesthingseasiertoread. This site describes the style used throughout the [tidyverse](http://tidyverse.org). It was derived from Google's original R Style Guide - but 4 | Google's [current guide](https://google.github.io/styleguide/Rguide.html) is derived from the tidyverse style guide. 5 | 6 | All style guides are fundamentally opinionated. Some decisions genuinely do make code easier to use (especially matching indenting to programming structure), but many decisions are arbitrary. The most important thing about a style guide is that it provides consistency, making code easier to write because you need to make fewer decisions. 7 | 8 | Two R packages support this style guide: 9 | 10 | * [styler](http://styler.r-lib.org) allows you to interactively restyle 11 | selected text, files, or entire projects. It includes an RStudio add-in, 12 | the easiest way to re-style existing code. 13 | 14 | ```{r} 15 | #| eval: true 16 | #| echo: false 17 | #| fig-align: center 18 | knitr::include_graphics("styler-addin.png", dpi = 220) 19 | ``` 20 | 21 | 22 | * [lintr](https://github.com/r-lib/lintr) performs automated checks 23 | to confirm that you conform to the style guide. 24 | -------------------------------------------------------------------------------- /news.qmd: -------------------------------------------------------------------------------- 1 | # News 2 | 3 | Each user-facing change to a package should be accompanied by a bullet in `NEWS.md`. Minor changes to documentation don't need to be documented, but it's worthwhile to draw attention to sweeping changes and to new vignettes. 4 | 5 | ## In-development 6 | 7 | The goal of the bullet is to briefly describe the change so users of the packages can understand what's changed. This can be similar to the commit message, but written with a user (not developer) in mind. It's worth emphasizing this point --- the reader of your NEWS entries is likely unfamiliar with the day-to-day development work or internals of your package. Think carefully about how to concisely but clearly summarize what's changed and why it matters for them. If it doesn't matter (i.e. it's a purely internal change), you don't need a bullet. 8 | 9 | New bullets should be added to the top of the file (immediately under the first heading) and should be a single line. Organisation and wrapping will happen later, during the release process. 10 | 11 | ``` 12 | # haven (development version) 13 | 14 | * Second update. 15 | * First update. 16 | ``` 17 | 18 | If the bullet is related to an issue, include the issue number. If the 19 | contribution is a PR, and the author is not a package author, include the 20 | GitHub user name. Both items should be wrapped in parentheses and will generally 21 | come before the final period. 22 | 23 | ``` 24 | # Good 25 | * `ggsave()` now uses full argument names to avoid partial match warnings (@wch, #2355). 26 | 27 | # Bad 28 | * `ggsave()` now uses full argument names to avoid partial match warnings. 29 | 30 | * `ggsave()` now uses full argument names to avoid partial match warnings. (@wch, #2355) 31 | ``` 32 | 33 | ## Pre-release {#news-release} 34 | 35 | Prior to release, the NEWS file needs to be thoroughly proofread, groomed, and organised into sections. 36 | 37 | ### General style 38 | 39 | Strive to place the name of the function as close to the beginning of the bullet 40 | as possible. A consistent location makes the bullets easier to scan, and easier 41 | to organise prior to release. 42 | 43 | ``` 44 | # Good 45 | * `ggsave()` now uses full argument names to avoid partial match warning (#2355). 46 | 47 | # Bad 48 | * Fixed partial argument matches in `ggsave()` (#2355). 49 | ``` 50 | 51 | Lines should be wrapped to 80 characters, and each bullet should end in a full 52 | stop. 53 | 54 | Frame bullets positively (i.e. what now happens, not what used to happen), and 55 | use the present tense. 56 | 57 | ``` 58 | # Good 59 | * `ggsave()` now uses full argument names to avoid partial match warnings (#2355). 60 | 61 | # Bad 62 | * `ggsave()` no longer partially matches argument names (#2355). 63 | ``` 64 | 65 | Many news bullets will be a single sentence. This is typically adequate when describing a bug fix or minor improvement, but you may need more detail when describing a new feature. For more complex features, include longer examples in fenced code blocks (```` ``` ````). These will be useful inspiration when you later write the blog post. 66 | 67 | ```` 68 | # Good 69 | * In `stat_bin()`, `binwidth` now also takes functions. 70 | 71 | # Better 72 | * In `stat_bin()`, `binwidth` now also takes functions. The function is 73 | called with the scaled `x` values, and should return a single number. 74 | This makes it possible to use classical binwidth computations with ggplot2. 75 | 76 | # Best 77 | * In `stat_bin()`, `binwidth` now also takes functions. The function is 78 | called with the scaled `x` values, and should return a single number. 79 | With a little work, this makes it possible to use classical bin size 80 | computations with ggplot2. 81 | 82 | ```R 83 | sturges <- function(x) { 84 | rng <- range(x) 85 | bins <- nclass.Sturges(x) 86 | 87 | (rng[2] - rng[1]) / bins 88 | } 89 | ggplot(diamonds, aes(price)) + 90 | geom_histogram(binwidth = sturges) + 91 | facet_wrap(~cut) 92 | ``` 93 | ```` 94 | 95 | ### Code style 96 | 97 | Functions, arguments, and file names should be wrapped in backticks. Function names should include parentheses; omit "the argument" or "the function" 98 | 99 | ``` 100 | # Good 101 | * In `stat_bin()`, `binwidth` now also takes functions. 102 | 103 | # Bad 104 | * In the stat_bin function, "binwidth" now also takes functions. 105 | ``` 106 | 107 | ### Headings 108 | 109 | Each release should have a level 1 heading (`#`) containing the package name 110 | and version number. For smaller packages or patch releases this amount of 111 | organisation may be sufficient. For example, here is the NEWS for modelr 0.1.2: 112 | 113 | ``` 114 | # modelr 0.1.2 115 | 116 | * `data_grid()` no longer fails with modern tidyr (#58). 117 | 118 | * New `mape()` and `rsae()` model quality statistics (@paulponcet, #33). 119 | 120 | * `rsquare()` use more robust calculation 1 - SS_res / SS_tot rather 121 | than SS_reg / SS_tot (#37). 122 | 123 | * `typical()` gains `ordered` and `integer` methods (@jrnold, #44), 124 | and `...` argument (@jrnold, #42). 125 | ``` 126 | 127 | If there are many bullets, the version heading should be followed by issues 128 | grouped into related areas with level 2 headings (`##`). Three commonly 129 | used sections are shown below: 130 | 131 | ``` 132 | # package 1.1.0 133 | 134 | ## Breaking changes 135 | 136 | ## New features 137 | 138 | ## Minor improvements and fixes 139 | ``` 140 | 141 | It is fine to deviate from these headings if another organisation makes sense. 142 | Indeed, larger packages will often require a finer break down. For example, 143 | ggplot2 2.3.0 144 | included these headings: 145 | 146 | ``` 147 | # ggplot 2.3.0 148 | ## Breaking changes 149 | ## New features 150 | ### Tidy evaluation 151 | ### sf 152 | ### Layers: geoms, stats, and position adjustments 153 | ### Scales and guides 154 | ### Margins 155 | ## Extension points 156 | ## Minor bug fixes and improvements 157 | ### Facetting 158 | ### Scales 159 | ### Layers 160 | ### Coords 161 | ### Themes 162 | ### Guides 163 | ### Other 164 | ``` 165 | 166 | It is not worthwhile to organise bullets into headings during development, as 167 | it's not typically obvious what the groups will be in advance. 168 | 169 | Within a section, bullets should be ordered alphabetically by the first function 170 | mentioned. If no function is mentioned, place the bullet at the top of the section. 171 | 172 | ### Breaking changes 173 | 174 | API breaking changes should also appear in their own section at the top. 175 | Each bullet should include a description of the symptoms of the change, and what 176 | is needed to fix it. The bullet should also be repeated in the appropriate section. 177 | 178 | ``` 179 | ## Breaking changes 180 | 181 | * `separate()` now correctly uses -1 to refer to the far right position, 182 | instead of -2. If you depended on this behaviour, you'll need to condition 183 | on `packageVersion("tidyr") > "0.7.2"`. 184 | ``` 185 | 186 | ### Common patterns 187 | 188 | The following excerpts from tidyverse news entries provide helpful templates to 189 | follow. 190 | 191 | * New family of functions: 192 | 193 | ``` 194 | * Support for ordered factors is improved. Ordered factors throw a warning 195 | when mapped to shape (unordered factors do not), and do not throw warnings 196 | when mapped to size or alpha (unordered factors do). Viridis is used as 197 | default colour and fill scale for ordered factors (@karawoo, #1526). 198 | 199 | * `possibly()`, `safely()` and friends no longer capture interrupts: this 200 | means that you can now terminate a mapper using one of these with 201 | Escape or Ctrl + C (#314). 202 | ``` 203 | 204 | * New function: 205 | 206 | ``` 207 | * New `position_dodge2()` provides enhanced dogding for boxplots... 208 | 209 | * New `stat_qq_line()` makes it easy to add a simple line to a Q-Q plot. 210 | This line makes it easier to judge the fit of the theoretical distribution 211 | (@nicksolomon). 212 | ``` 213 | 214 | * New argument to existing function: 215 | 216 | ``` 217 | * `geom_segment()` gains a `linejoin` parameter. 218 | ``` 219 | 220 | * Function argument changes behaviour: 221 | 222 | ``` 223 | * In `separate()`, `col = -1` now refers to the far right position. 224 | Previously, and incorrectly, `col = -2` referred to the far-right 225 | position. 226 | ``` 227 | 228 | * Function changes behaviour: 229 | 230 | ``` 231 | * `map()` and `modify()` now work with calls and pairlists (#412). 232 | 233 | * `flatten_dfr()` and `flatten_dfc()` now aborts with informative 234 | message if dplyr is not installed (#454). 235 | 236 | * `reduce()` now throws an error if `.x` is empty and `.init` is not 237 | supplied. 238 | ``` 239 | 240 | ## Blog post 241 | 242 | For all major and minor releases, the latest news should be turned into a blog 243 | post. The blog post should highlight major user-facing changes, and point to 244 | the release notes for more details. Generally, you should focus on new features 245 | and major improvements, including examples showing the new features in action. 246 | You don't need to describe minor improvements and bug fixes, as the motivated 247 | reader can find these in the release notes. 248 | -------------------------------------------------------------------------------- /package-files.qmd: -------------------------------------------------------------------------------- 1 | # Files {#package-files} 2 | 3 | The majority of advice in @sec-files also applies to files in packages. Important differences are described below. 4 | 5 | ## Names 6 | 7 | * If a file contains a single function, give the file the same name as the 8 | function. 9 | 10 | * If a file contains multiple related functions, give it a concise, 11 | but evocative name. 12 | 13 | * Deprecated functions should live in a file with `deprec-` prefix. 14 | 15 | ## Organisation 16 | 17 | In a file that contains multiple functions, public functions and their 18 | documentation should appear first, with private functions appearing after all 19 | documented functions. If multiple public functions share the same documentation, 20 | they should all immediately follow the documentation block. 21 | 22 | See @sec-documentation for more thorough guidance on documenting functions 23 | in packages. 24 | 25 | ```{r} 26 | # Bad 27 | help_compute <- function() { 28 | # ... Lots of code ... 29 | } 30 | 31 | #' My public function 32 | #' 33 | #' This is where the documentation of my function begins. 34 | #' ... 35 | #' @export 36 | do_something_cool <- function() { 37 | # ... even more code ... 38 | help_compute() 39 | } 40 | ``` 41 | 42 | ```{r} 43 | # Good 44 | #' Lots of functions for doing something cool 45 | #' 46 | #' ... Complete documentation ... 47 | #' @name something-cool 48 | NULL 49 | 50 | #' @describeIn something-cool Get the mean 51 | #' @export 52 | get_cool_mean <- function(x) { 53 | # ... 54 | } 55 | 56 | #' @describeIn something-cool Get the sum 57 | #' @export 58 | get_cool_sum <- function(x) { 59 | # ... 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /pipes.qmd: -------------------------------------------------------------------------------- 1 | # Pipes 2 | 3 | ## Introduction 4 | 5 | Use `|>` to emphasise a sequence of actions, rather than the object that the actions are being performed on. 6 | 7 | The tidyverse has been designed to work particularly well with the pipe, but you can use it with any code, particularly in conjunction with the `_` placeholder. 8 | 9 | ```{r} 10 | strings |> 11 | str_replace("a", "b") |> 12 | str_replace("x", "y") 13 | 14 | strings |> 15 | gsub("a", "b", x = _) |> 16 | gsub("x", "y", x = _) 17 | ``` 18 | 19 | Avoid using the pipe when: 20 | 21 | * You need to manipulate more than one object at a time. Reserve pipes for a 22 | sequence of steps applied to one primary object. 23 | 24 | * There are meaningful intermediate objects that could be given 25 | informative names. 26 | 27 | ## Whitespace 28 | 29 | `|>` should always have a space before it, and should usually be followed by a new line. After the first step, each line should be indented by two spaces. This structure makes it easier to add new steps (or rearrange existing steps) and harder to overlook a step. 30 | 31 | ```{r} 32 | # Good 33 | iris |> 34 | summarize(across(where(is.numeric), mean), .by = Species) |> 35 | pivot_longer(!Species, names_to = "measure", values_to = "value") |> 36 | arrange(value) 37 | 38 | # Bad 39 | iris |> summarize(across(where(is.numeric), mean), .by = Species) |> 40 | pivot_longer(!Species, names_to = "measure", values_to = "value") |> 41 | arrange(value) 42 | ``` 43 | 44 | ## Long lines 45 | 46 | If the arguments to a function don't all fit on one line, put each argument on 47 | its own line and indent: 48 | 49 | ```{r} 50 | # Good 51 | iris |> 52 | summarise( 53 | Sepal.Length = mean(Sepal.Length), 54 | Sepal.Width = mean(Sepal.Width), 55 | .by = Species 56 | ) 57 | 58 | # Bad 59 | iris |> 60 | summarise(Sepal.Length = mean(Sepal.Length), Sepal.Width = mean(Sepal.Width), .by = Species) 61 | ``` 62 | 63 | For data analysis, we recommend using the pipe whenever a function needs to span multiple lines, even if it's only a single step. 64 | 65 | ```{r} 66 | # Bad 67 | summarise( 68 | iris, 69 | Sepal.Length = mean(Sepal.Length), 70 | Sepal.Width = mean(Sepal.Width), 71 | .by = Species 72 | ) 73 | ``` 74 | 75 | ## Short pipes 76 | 77 | It's ok to write a short pipe on a single line: 78 | 79 | ```{r} 80 | # Ok 81 | iris |> subset(Species == "virginica") |> _$Sepal.Length 82 | iris |> summarise(width = Sepal.Width, .by = Species) |> arrange(width) 83 | ``` 84 | 85 | But because short pipes often become longer pipes, we recommend that you generally stick to one function per line: 86 | 87 | ```{r} 88 | # Better 89 | iris |> 90 | subset(Species == "virginica") |> 91 | _$Sepal.Length 92 | 93 | iris |> 94 | summarise(width = Sepal.Width, .by = Species) |> 95 | arrange(width) 96 | ``` 97 | 98 | Sometimes it's useful to include a short pipe as an argument to a function in a 99 | longer pipe. Carefully consider whether the code is more readable with a short 100 | inline pipe (which doesn't require a lookup elsewhere) or if it's better to move 101 | the code outside the pipe and give it an evocative name. 102 | 103 | ```{r} 104 | # Good 105 | x |> 106 | semi_join(y |> filter(is_valid)) 107 | 108 | # Ok 109 | x |> 110 | select(a, b, w) |> 111 | left_join(y |> select(a, b, v), join_by(a, b)) 112 | 113 | # Better 114 | x_join <- x |> select(a, b, w) 115 | y_join <- y |> select(a, b, v) 116 | left_join(x_join, y_join, join_by(a, b)) 117 | ``` 118 | 119 | ## Assignment 120 | 121 | There are three acceptable forms of assignment: 122 | 123 | * Variable name and assignment on separate lines: 124 | 125 | ```{r} 126 | iris_long <- 127 | iris |> 128 | gather(measure, value, -Species) |> 129 | arrange(-value) 130 | ``` 131 | 132 | * Variable name and assignment on the same line: 133 | 134 | ```{r} 135 | iris_long <- iris |> 136 | gather(measure, value, -Species) |> 137 | arrange(-value) 138 | ``` 139 | 140 | * Assignment at the end of the pipe with `->`: 141 | 142 | ```{r} 143 | iris |> 144 | gather(measure, value, -Species) |> 145 | arrange(-value) -> 146 | iris_long 147 | ``` 148 | 149 | I think that the third is the most natural to write, but makes reading a little 150 | harder: when the name comes first, it can act as a heading to remind 151 | you of the purpose of the pipe. 152 | 153 | ## magrittr 154 | 155 | We recommend you use the base `|>` pipe instead of magrittr's `%>%`. 156 | 157 | ```{r} 158 | # Good 159 | iris |> 160 | summarise(width = Sepal.Width, .by = Species) |> 161 | arrange(width) 162 | 163 | # Bad 164 | iris %>% 165 | summarise(width = Sepal.Width, .by = Species) %>% 166 | arrange(width) 167 | ``` 168 | 169 | As of R 4.3.0, the base pipe provides all the features from magrittr that we recommend using. 170 | -------------------------------------------------------------------------------- /plausible.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /style.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | BuildType: Website 16 | -------------------------------------------------------------------------------- /styler-addin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tidyverse/style/8d69afc7c8a09b6ab415820b869ca1ef7099ec6a/styler-addin.png -------------------------------------------------------------------------------- /styles.scss: -------------------------------------------------------------------------------- 1 | /*-- scss:rules --*/ 2 | 3 | .sidebar-title { 4 | color: #301445; 5 | } 6 | 7 | div.sidebar-item-container .active { 8 | font-weight: bold; 9 | } 10 | 11 | .sidebar nav[role=doc-toc] ul>li>a.active, .sidebar nav[role=doc-toc] ul>li>ul>li>a.active{ 12 | font-weight: bold; 13 | } 14 | 15 | img.quarto-cover-image { 16 | box-shadow: 0 .5rem 1rem rgba(0,0,0,.15); 17 | } 18 | 19 | /* Headings ------------------------------------------------------ */ 20 | 21 | #title-block-header.quarto-title-block.default .quarto-title h1.title { 22 | margin-bottom: 0.5rem; 23 | } 24 | 25 | h2 { 26 | margin-top: 2rem; 27 | margin-bottom: 1rem; 28 | font-size: 1.4rem; 29 | font-weight: 600; 30 | } 31 | h3 { margin-top: 1.5em; font-size: 1.2rem; font-weight: 500; } 32 | h4 { margin-top: 1.5em; font-size: 1.1rem; } 33 | h5 { margin-top: 1.5em; font-size: 1rem; } 34 | 35 | .quarto-section-identifier { 36 | color: #6C6C6C; 37 | font-weight: normal; 38 | } 39 | 40 | /* Code ------------------------------------------------ */ 41 | 42 | code { 43 | color: #3a373c; 44 | } 45 | 46 | code a:any-link { 47 | text-decoration: underline; 48 | text-decoration-color: #ccc; 49 | } 50 | 51 | pre { 52 | background-image: linear-gradient(160deg,#f8f8f8 0,#f1f1f1 100%); 53 | } 54 | -------------------------------------------------------------------------------- /syntax.qmd: -------------------------------------------------------------------------------- 1 | # Syntax 2 | 3 | ## Object names {#sec-objectnames} 4 | 5 | > "There are only two hard things in Computer Science: cache invalidation and 6 | > naming things." 7 | > 8 | > --- Phil Karlton 9 | 10 | Variable and function names should use only lowercase letters, numbers, and `_`. 11 | Use underscores (`_`) (so called snake case) to separate words within a name. 12 | 13 | ```{r} 14 | # Good 15 | day_one 16 | day_1 17 | 18 | # Bad 19 | DayOne 20 | dayone 21 | ``` 22 | 23 | Base R uses dots in function names (`contrib.url()`) and class names 24 | (`data.frame`), but it's better to reserve dots exclusively for the S3 object 25 | system. In S3, methods are given the name `function.class`; if you also use 26 | `.` in function and class names, you end up with confusing methods like 27 | `as.data.frame.data.frame()`. 28 | 29 | If you find yourself attempting to cram data into variable names (e.g. `model_2018`, `model_2019`, `model_2020`), consider using a list or data frame instead. 30 | 31 | Generally, variable names should be nouns and function names should be verbs. 32 | Strive for names that are concise and meaningful (this is not easy!). 33 | 34 | ```{r} 35 | # Good 36 | day_one 37 | 38 | # Bad 39 | first_day_of_the_month 40 | djm1 41 | ``` 42 | 43 | Where possible, avoid re-using names of common functions and variables. This 44 | will cause confusion for the readers of your code. 45 | 46 | ```{r} 47 | # Bad 48 | T <- FALSE 49 | c <- 10 50 | mean <- function(x) sum(x) 51 | ``` 52 | 53 | ## Spacing 54 | 55 | ### Commas 56 | 57 | Always put a space after a comma, never before, just like in regular English. 58 | 59 | ```{r} 60 | # Good 61 | x[, 1] 62 | 63 | # Bad 64 | x[,1] 65 | x[ ,1] 66 | x[ , 1] 67 | ``` 68 | 69 | ### Parentheses 70 | 71 | Do not put spaces inside or outside parentheses for regular function calls. 72 | 73 | ```{r} 74 | # Good 75 | mean(x, na.rm = TRUE) 76 | 77 | # Bad 78 | mean (x, na.rm = TRUE) 79 | mean( x, na.rm = TRUE ) 80 | ``` 81 | 82 | Place a space before and after `()` when used with `if`, `for`, or `while`. 83 | 84 | ```{r} 85 | # Good 86 | if (debug) { 87 | show(x) 88 | } 89 | 90 | # Bad 91 | if(debug){ 92 | show(x) 93 | } 94 | ``` 95 | 96 | Place a space after `()` used for function arguments: 97 | 98 | ```{r} 99 | # Good 100 | function(x) {} 101 | 102 | # Bad 103 | function (x) {} 104 | function(x){} 105 | ``` 106 | 107 | ### Embracing 108 | 109 | The embracing operator, `{{ }}`, should always have inner spaces to help emphasise its special behaviour: 110 | 111 | ```{r} 112 | # Good 113 | max_by <- function(data, var, by) { 114 | data |> 115 | group_by({{ by }}) |> 116 | summarise(maximum = max({{ var }}, na.rm = TRUE)) 117 | } 118 | 119 | # Bad 120 | max_by <- function(data, var, by) { 121 | data |> 122 | group_by({{by}}) |> 123 | summarise(maximum = max({{var}}, na.rm = TRUE)) 124 | } 125 | ``` 126 | 127 | 128 | ### Infix operators 129 | 130 | Most infix operators (`==`, `+`, `-`, `<-`, etc.) should always be surrounded by 131 | spaces: 132 | 133 | ```{r} 134 | # Good 135 | height <- (feet * 12) + inches 136 | mean(x, na.rm = TRUE) 137 | 138 | # Bad 139 | height<-feet*12+inches 140 | mean(x, na.rm=TRUE) 141 | ``` 142 | 143 | There are a few exceptions, which should never be surrounded by spaces: 144 | 145 | * The operators with [high precedence][syntax]: `::`, `:::`, `$`, `@`, `[`, 146 | `[[`, `^`, unary `-`, unary `+`, and `:`. 147 | 148 | ```{r} 149 | # Good 150 | sqrt(x^2 + y^2) 151 | df$z 152 | x <- 1:10 153 | 154 | # Bad 155 | sqrt(x ^ 2 + y ^ 2) 156 | df $ z 157 | x <- 1 : 10 158 | ``` 159 | 160 | * Single-sided formulas when the right-hand side is a single identifier. 161 | 162 | ```{r} 163 | # Good 164 | ~foo 165 | tribble( 166 | ~col1, ~col2, 167 | "a", "b" 168 | ) 169 | 170 | # Bad 171 | ~ foo 172 | tribble( 173 | ~ col1, ~ col2, 174 | "a", "b" 175 | ) 176 | ``` 177 | 178 | Note that single-sided formulas with a complex right-hand side do need a space. 179 | 180 | ```{r} 181 | # Good 182 | ~ .x + .y 183 | 184 | # Bad 185 | ~.x + .y 186 | ``` 187 | 188 | * When used in tidy evaluation `!!` (bang-bang) and `!!!` (bang-bang-bang) 189 | (because they have precedence equivalent to unary `-`/`+`). 190 | 191 | ```{r} 192 | # Good 193 | call(!!xyz) 194 | 195 | # Bad 196 | call(!! xyz) 197 | call( !! xyz) 198 | call(! !xyz) 199 | ``` 200 | 201 | * The help operator. 202 | 203 | ```{r} 204 | # Good 205 | package?stats 206 | ?mean 207 | 208 | # Bad 209 | package ? stats 210 | ? mean 211 | ``` 212 | 213 | ### Extra spaces 214 | 215 | Adding extra spaces is ok if it improves alignment of `=` or `<-`. 216 | 217 | ```{r} 218 | # Good 219 | list( 220 | total = a + b + c, 221 | mean = (a + b + c) / n 222 | ) 223 | 224 | # Also fine 225 | list( 226 | total = a + b + c, 227 | mean = (a + b + c) / n 228 | ) 229 | ``` 230 | 231 | Do not add extra spaces to places where space is not usually allowed. 232 | 233 | ## Vertical space 234 | 235 | Use vertical whitespace sparingly, and primarily to separate your "thoughts" in code, much like paragraph breaks in prose. 236 | 237 | * Avoid empty lines at the start or end of functions. 238 | * Only use a single empty line when needed to separate functions or pipes. 239 | * It often makes sense to put an empty line before a comment block, to help visually connect the explanation with the code that it applies to. 240 | 241 | ## Function calls 242 | 243 | ### Named arguments {#argument-names} 244 | 245 | A function's arguments typically fall into two broad categories: one supplies 246 | the __data__ to compute on; the other controls the __details__ of computation. 247 | When you call a function, you typically omit the names of data arguments, 248 | because they are used so commonly. If you override the default value of an 249 | argument, use the full name: 250 | 251 | ```{r} 252 | # Good 253 | mean(1:10, na.rm = TRUE) 254 | 255 | # Bad 256 | mean(x = 1:10, , FALSE) 257 | mean(, TRUE, x = c(1:10, NA)) 258 | ``` 259 | 260 | Avoid partial matching, where you supply a unique prefix of a function argument. 261 | 262 | ```{r} 263 | # Good 264 | rep(1:2, times = 3) 265 | cut(1:10, breaks = c(0, 4, 11)) 266 | 267 | # Bad 268 | rep(1:2, t = 3) 269 | cut(1:10, br = c(0, 4, 11)) 270 | ``` 271 | 272 | ### Assignment 273 | 274 | Avoid assignment in function calls: 275 | 276 | ```{r} 277 | # Good 278 | x <- complicated_function() 279 | if (nzchar(x) < 1) { 280 | # do something 281 | } 282 | 283 | # Bad 284 | if (nzchar(x <- complicated_function()) < 1) { 285 | # do something 286 | } 287 | ``` 288 | 289 | The only exception is in functions that capture side-effects: 290 | 291 | ```{r} 292 | output <- capture.output(x <- f()) 293 | ``` 294 | 295 | ### Long function calls 296 | 297 | Strive to limit your code to 80 characters per line. This fits comfortably on a 298 | printed page with a reasonably sized font. If you find yourself running out of 299 | room, this is a good indication that you should encapsulate some of the work in 300 | a separate function or use early returns to reduce the nesting in your code. 301 | 302 | If a function call is too long to fit on a single line, use one line each for 303 | the function name, each argument, and the closing `)`. 304 | This makes the code easier to read and to change later. 305 | 306 | ```{r} 307 | # Good 308 | do_something_very_complicated( 309 | something = "that", 310 | requires = many, 311 | arguments = "some of which may be long" 312 | ) 313 | 314 | # Bad 315 | do_something_very_complicated("that", requires, many, arguments, 316 | "some of which may be long" 317 | ) 318 | ``` 319 | 320 | As described under [Named arguments](#argument-names), you can omit the argument names 321 | for very common arguments (i.e. for arguments that are used in almost every 322 | invocation of the function). If this introduces a large disparity between the line lengths, you may want to supply names anyway: 323 | 324 | ```{r} 325 | # Good 326 | my_function( 327 | x, 328 | long_argument_name, 329 | extra_argument_a = 10, 330 | extra_argument_b = c(1, 43, 390, 210209) 331 | ) 332 | 333 | # Also good 334 | my_function( 335 | x = x, 336 | y = long_argument_name, 337 | extra_argument_a = 10, 338 | extra_argument_b = c(1, 43, 390, 210209) 339 | ) 340 | ``` 341 | 342 | You may place multiple unnamed arguments on the same line if they are closely 343 | related to each other. A common example of this is creating strings 344 | with `paste()`. In such cases, it's often beneficial to match one line of code 345 | to one line of output. 346 | 347 | ```{r} 348 | # Good 349 | paste0( 350 | "Requirement: ", requires, "\n", 351 | "Result: ", result, "\n" 352 | ) 353 | 354 | # Bad 355 | paste0( 356 | "Requirement: ", requires, 357 | "\n", "Result: ", 358 | result, "\n") 359 | ``` 360 | 361 | ## Braced expressions {#braced-expressions} 362 | 363 | Braced expressions, `{}`, define the most important hierarchy of R code, allowing you to group multiple R expressions together into a single expression. The most common places to use braced expressions are in function definitions, control flow, and in certain function calls (e.g. `tryCatch()` and `test_that()`). 364 | 365 | To make this hierarchy easy to see: 366 | 367 | * `{` should be the last character on the line. 368 | Related code (e.g., an `if` clause, a function declaration, a trailing comma, ...) must be on the same line as the opening brace. 369 | 370 | * The contents should be indented by two spaces. 371 | 372 | * `}` should be the first character on the line. 373 | 374 | ```{r} 375 | # Good 376 | if (y < 0 && debug) { 377 | message("y is negative") 378 | } 379 | 380 | if (y == 0) { 381 | if (x > 0) { 382 | log(x) 383 | } else { 384 | message("x is negative or zero") 385 | } 386 | } else { 387 | y^x 388 | } 389 | 390 | test_that("call1 returns an ordered factor", { 391 | expect_s3_class(call1(x, y), c("factor", "ordered")) 392 | }) 393 | 394 | tryCatch( 395 | { 396 | x <- scan() 397 | cat("Total: ", sum(x), "\n", sep = "") 398 | }, 399 | interrupt = function(e) { 400 | message("Aborted by user") 401 | } 402 | ) 403 | 404 | # Bad 405 | if (y < 0 && debug) { 406 | message("Y is negative") 407 | } 408 | 409 | if (y == 0) 410 | { 411 | if (x > 0) { 412 | log(x) 413 | } else { 414 | message("x is negative or zero") 415 | } 416 | } else { y ^ x } 417 | ``` 418 | 419 | It is occasionally useful to have empty braced expressions, in which case it should be written `{}`, with no intervening space. 420 | 421 | ```{r} 422 | # Good 423 | function(...) {} 424 | 425 | # Bad 426 | function(...) { } 427 | function(...) { 428 | 429 | } 430 | ``` 431 | 432 | ## Control flow 433 | 434 | ### Loops 435 | 436 | R defines three types of looping constructs: `for`, `while`, and `repeat` loops. 437 | 438 | * The body of a loop must be a braced expression. 439 | 440 | ```{r} 441 | # Good 442 | for (i in seq) { 443 | x[i] <- x[i] + 1 444 | } 445 | 446 | while (waiting_for_something()) { 447 | cat("Still waiting...") 448 | } 449 | 450 | # Bad 451 | for (i in seq) x[i] <- x[i] + 1 452 | 453 | while (waiting_for_something()) cat("Still waiting...") 454 | ``` 455 | 456 | * It is occasionally useful to use a `while` loop with an empty braced expression body to wait. As mentioned in [Braced expressions](#braced-expressions), there should be no space within the `{}`. 457 | 458 | ### If statements 459 | 460 | * A single line if statement must never contain braced expressions. You can use 461 | single line if statements for very simple statements that don't have 462 | side-effects and don't modify the control flow. 463 | 464 | ```{r} 465 | # Good 466 | message <- if (x > 10) "big" else "small" 467 | 468 | # Bad 469 | message <- if (x > 10) { "big" } else { "small" } 470 | 471 | if (x > 0) message <- "big" else message <- "small" 472 | 473 | if (x > 0) return(x) 474 | ``` 475 | 476 | * A multiline if statement must contain braced expressions. 477 | 478 | ```{r} 479 | # Good 480 | if (x > 10) { 481 | x * 2 482 | } 483 | 484 | if (x > 10) { 485 | x * 2 486 | } else { 487 | x * 3 488 | } 489 | 490 | # Bad 491 | if (x > 10) 492 | x * 2 493 | 494 | # In particular, this if statement will only parse when wrapped in a braced 495 | # expression or call 496 | { 497 | if (x > 10) 498 | x * 2 499 | else 500 | x * 3 501 | } 502 | ``` 503 | 504 | * When present, `else` should be on the same line as `}`. 505 | 506 | * Avoid implicit type coercion (e.g. from numeric to logical) in the condition of an if statement: 507 | 508 | ```{r} 509 | # Good 510 | if (length(x) > 0) { 511 | # do something 512 | } 513 | 514 | # Bad 515 | if (length(x)) { 516 | # do something 517 | } 518 | ``` 519 | 520 | ::: {.callout-note} 521 | `&` and `|` should never be used inside of an `if` clause because they can return vectors. Always use `&&` and `||` instead. 522 | ::: 523 | 524 | ::: {.callout-note} 525 | `ifelse(x, a, b)` is not a drop-in replacement for `if (x) a else b`. `ifelse()` is vectorised (i.e. if `length(x) > 1`, then `a` and `b` will be recycled to match) and it is eager (i.e. both `a` and `b` will always be evaluated). 526 | ::: 527 | 528 | ### Control flow modifiers {#control-flow-modifiers} 529 | 530 | Syntax that affects control flow (like `return()`, `stop()`, `break`, or `next`) should always go in their own `{}` block: 531 | 532 | ```{r} 533 | # Good 534 | if (y < 0) { 535 | stop("Y is negative") 536 | } 537 | 538 | find_abs <- function(x) { 539 | if (x > 0) { 540 | return(x) 541 | } 542 | x * -1 543 | } 544 | 545 | for (x in xs) { 546 | if (is_done(x)) { 547 | break 548 | } 549 | } 550 | 551 | # Bad 552 | if (y < 0) stop("Y is negative") 553 | 554 | find_abs <- function(x) { 555 | if (x > 0) return(x) 556 | x * -1 557 | } 558 | 559 | for (x in xs) { 560 | if (is_done(x)) break 561 | } 562 | ``` 563 | 564 | ### Switch statements 565 | 566 | * Avoid position-based `switch()` statements (i.e. prefer names). 567 | * Each element should go on its own line unless all element can fit on one line. 568 | * Elements that fall through to the following element should have a space after `=`. 569 | * Provide a fall-through error unless you have previously validated the input. 570 | 571 | ```{r} 572 | # Good 573 | switch(x, 574 | a = , 575 | b = 1, 576 | c = 2, 577 | stop("Unknown `x`", call. = FALSE) 578 | ) 579 | 580 | # Bad 581 | switch(x, 582 | a =, 583 | b = 1, 584 | c = 2 585 | ) 586 | switch(x, 587 | a = long_function_name1(), b = long_function_name2(), 588 | c = long_function_name2() 589 | ) 590 | switch(y, 1, 2, 3) 591 | ``` 592 | 593 | ## Semicolons 594 | 595 | Semicolons are never recommended. 596 | In particular, don't put `;` at the end of a line, and don't use `;` to put multiple commands on one line. 597 | 598 | ```{r} 599 | # Good 600 | my_helper() 601 | my_other_helper() 602 | 603 | # Bad 604 | my_helper(); 605 | my_other_helper(); 606 | 607 | { my_helper(); my_other_helper() } 608 | ``` 609 | 610 | ## Assignment 611 | 612 | Use `<-`, not `=`, for assignment. 613 | 614 | ```{r} 615 | # Good 616 | x <- 5 617 | 618 | # Bad 619 | x = 5 620 | ``` 621 | 622 | ## Data 623 | 624 | ### Character vectors 625 | 626 | Use `"`, not `'`, for quoting text. The only exception is when the text already 627 | contains double quotes and no single quotes. 628 | 629 | ```{r} 630 | # Good 631 | "Text" 632 | 'Text with "quotes"' 633 | 'A link' 634 | 635 | # Bad 636 | 'Text' 637 | 'Text with "double" and \'single\' quotes' 638 | ``` 639 | 640 | ### Logical vectors 641 | 642 | Prefer `TRUE` and `FALSE` over `T` and `F`. 643 | 644 | ## Comments 645 | 646 | Each line of a comment should begin with the comment symbol and a single 647 | space: `# ` 648 | 649 | In data analysis code, use comments to record important findings and analysis 650 | decisions. If you need comments to explain what your code is doing, consider 651 | rewriting your code to be clearer. If you discover that you have more comments 652 | than code, consider switching to [R Markdown][rmd]. 653 | 654 | [syntax]: https://rdrr.io/r/base/Syntax.html 655 | [rmd]: https://rmarkdown.rstudio.com/ 656 | -------------------------------------------------------------------------------- /tests.qmd: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | ## Organisation 4 | 5 | The organisation of test files should match the organisation of `R/` files: if a function lives in `R/foofy.R`, then its tests should live in `tests/testthat/test-foofy.R`. 6 | 7 | Use `usethis::use_test()` to automatically create a file with the correct name. 8 | 9 | The file name will be displayed in output in order to get context. 10 | --------------------------------------------------------------------------------