├── .Rbuildignore ├── .Rproj.user ├── 1B21F6B0 │ ├── build_options │ ├── console06 │ │ └── INDEX001 │ ├── cpp-definition-cache │ ├── pcs │ │ ├── debug-breakpoints.pper │ │ ├── files-pane.pper │ │ ├── source-pane.pper │ │ ├── windowlayoutstate.pper │ │ └── workbench-pane.pper │ ├── persistent-state │ ├── rmd-outputs │ ├── saved_source_markers │ └── sources │ │ └── prop │ │ ├── 54F4CD67 │ │ ├── 5F2748F1 │ │ ├── 610E5171 │ │ ├── 79F4A964 │ │ ├── 820C4FC0 │ │ ├── 83D3127E │ │ ├── 9AAD83DA │ │ ├── AFA24831 │ │ ├── B005AC79 │ │ ├── E7798880 │ │ ├── E7DD2F97 │ │ ├── EB65C34A │ │ └── INDEX └── shared │ └── notebooks │ ├── 175BACE8-README │ └── 1 │ │ └── s │ │ └── chunks.json │ ├── 3531CF06-using-cheatR │ └── 1 │ │ └── s │ │ └── chunks.json │ ├── patch-chunk-names │ └── paths ├── .gitattributes ├── .github ├── .gitignore └── workflows │ ├── R-check.yaml │ ├── pkgdown.yml │ └── render-readme.yaml ├── .gitignore ├── DESCRIPTION ├── NAMESPACE ├── NEWS.md ├── R ├── catch_em.R ├── methods.R ├── onload.R └── shiny.R ├── README.Rmd ├── README.md ├── _config.yml ├── _pkgdown.yml ├── cheatR.Rproj ├── docs ├── 404.html ├── articles │ ├── index.html │ ├── using-cheatR.html │ └── using-cheatR_files │ │ └── figure-html │ │ └── cheater_graph-1.png ├── authors.html ├── bootstrap-toc.css ├── bootstrap-toc.js ├── docsearch.css ├── docsearch.js ├── index.html ├── link.svg ├── news │ └── index.html ├── pkgdown.css ├── pkgdown.js ├── pkgdown.yml └── reference │ ├── catch_em.html │ ├── catch_em_app.html │ ├── compare_txt.html │ ├── figures │ ├── cheatRball.png │ ├── cheater_graph-1.png │ └── shiny_app.PNG │ ├── hist.chtrs.html │ ├── index.html │ ├── plot.chtrs.html │ ├── print.chtrs.html │ └── summary.chtrs.html ├── man ├── catch_em.Rd ├── catch_em_app.Rd ├── compare_txt.Rd ├── figures │ ├── cheatRball.png │ ├── cheater_graph-1.png │ └── shiny_app.PNG ├── files │ ├── paper1_copy1.docx │ ├── paper1_copy2.docx │ ├── paper1_copy3.docx │ └── paper2_copy1.docx ├── plot.chtrs.Rd └── summary.chtrs.Rd ├── tests ├── testthat.R └── testthat │ └── test-chatch_em.R └── vignettes ├── .gitignore └── using-cheatR.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^\.Rprofile$ 2 | ^\.github 3 | ^.*\.Rproj$ 4 | ^\.Rproj\.user$ 5 | ^\.travis.yml 6 | ^\_pkgdown.yml 7 | ^LICENSE 8 | ^data/. 9 | ^docs/. 10 | ^README.Rmd 11 | ^pkgdown/. 12 | ^paper.*$ 13 | ^WIP$ -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/build_options: -------------------------------------------------------------------------------- 1 | auto_roxygenize_for_build_and_reload="1" 2 | auto_roxygenize_for_build_package="1" 3 | auto_roxygenize_for_check="1" 4 | live_preview_website="1" 5 | makefile_args="" 6 | preview_website="1" 7 | website_output_format="all" 8 | -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/console06/INDEX001: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/cpp-definition-cache: -------------------------------------------------------------------------------- 1 | [ 2 | ] -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/pcs/debug-breakpoints.pper: -------------------------------------------------------------------------------- 1 | { 2 | "debugBreakpointsState" : { 3 | "breakpoints" : [ 4 | ] 5 | } 6 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/pcs/files-pane.pper: -------------------------------------------------------------------------------- 1 | { 2 | "path" : "~/R/cheatR/vignettes", 3 | "sortOrder" : [ 4 | { 5 | "ascending" : true, 6 | "columnIndex" : 2 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/pcs/source-pane.pper: -------------------------------------------------------------------------------- 1 | { 2 | "activeTab" : -1 3 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/pcs/windowlayoutstate.pper: -------------------------------------------------------------------------------- 1 | { 2 | "left" : { 3 | "panelheight" : 646, 4 | "splitterpos" : 252, 5 | "topwindowstate" : "HIDE", 6 | "windowheight" : 684 7 | }, 8 | "right" : { 9 | "panelheight" : 646, 10 | "splitterpos" : 324, 11 | "topwindowstate" : "NORMAL", 12 | "windowheight" : 684 13 | } 14 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/pcs/workbench-pane.pper: -------------------------------------------------------------------------------- 1 | { 2 | "TabSet1" : 4, 3 | "TabSet2" : 0, 4 | "TabZoom" : { 5 | } 6 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/persistent-state: -------------------------------------------------------------------------------- 1 | build-last-errors="[]" 2 | build-last-errors-base-dir="~/R/cheatR/" 3 | build-last-outputs="[{\"output\":\"==> devtools::document(roclets = c('rd', 'collate', 'namespace', 'vignette'))\\n\\n\",\"type\":0},{\"output\":\"Updating cheatR documentation\\r\\n\",\"type\":2},{\"output\":\"Writing NAMESPACE\\r\\n\",\"type\":1},{\"output\":\"Loading cheatR\\r\\n\",\"type\":2},{\"output\":\"Writing plot.chtrs.Rd\\r\\nWriting NAMESPACE\\r\\n\",\"type\":1},{\"output\":\"Updating vignettes\\r\\n\",\"type\":2},{\"output\":\"Documentation completed\\n\\n\",\"type\":1},{\"output\":\"==> Rcmd.exe INSTALL --no-multiarch --with-keep.source cheatR\\n\\n\",\"type\":0},{\"output\":\"* installing to library 'C:/Users/USER/Documents/R/win-library/3.6'\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"* installing *source* package 'cheatR' ...\\r\\n\",\"type\":1},{\"output\":\"** using staged installation\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"** R\\r\\n\",\"type\":1},{\"output\":\"** byte-compile and prepare package for lazy loading\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"** help\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"*** installing help indices\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\" converting help for package 'cheatR'\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\" finding HTML links ...\",\"type\":1},{\"output\":\" done\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\" catch_em html \",\"type\":1},{\"output\":\"\\r\\n\",\"type\":1},{\"output\":\" finding level-2 HTML links ...\",\"type\":1},{\"output\":\"\\r\\n\",\"type\":1},{\"output\":\" catch_em_app html \\r\\n\",\"type\":1},{\"output\":\" compare_txt html \",\"type\":1},{\"output\":\" done\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"\\r\\n\",\"type\":1},{\"output\":\" hist.chtrs html \\r\\n\",\"type\":1},{\"output\":\" plot.chtrs html \\r\\n\",\"type\":1},{\"output\":\" print.chtrs html \",\"type\":1},{\"output\":\"\\r\\n\",\"type\":1},{\"output\":\" summary.chtrs html \\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"** building package indices\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"** installing vignettes\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"** testing if installed package can be loaded from temporary location\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"** testing if installed package can be loaded from final location\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"** testing if installed package keeps a record of temporary installation path\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1},{\"output\":\"* DONE (cheatR)\\r\\n\",\"type\":1},{\"output\":\"\",\"type\":1}]" 4 | compile_pdf_state="{\"errors\":[],\"output\":\"\",\"running\":false,\"tab_visible\":false,\"target_file\":\"\"}" 5 | files.monitored-path="" 6 | find-in-files-state="{\"handle\":\"\",\"input\":\"\",\"path\":\"\",\"regex\":true,\"results\":{\"file\":[],\"line\":[],\"lineValue\":[],\"matchOff\":[],\"matchOn\":[]},\"running\":false}" 7 | imageDirtyState="1" 8 | saveActionState="-1" 9 | -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/rmd-outputs: -------------------------------------------------------------------------------- 1 | C:/Users/USER/AppData/Local/Temp/Rtmpg9Ts6l/preview-18183dbd18f.html 2 | C:/Users/USER/AppData/Local/Temp/Rtmpg9Ts6l/preview-1430506e7601.dir/using-cheatR.html 3 | C:/Users/USER/AppData/Local/Temp/Rtmpg9Ts6l/preview-2e9876b7fde.html 4 | C:/Users/USER/AppData/Local/Temp/Rtmpy8L2ly/preview-123421125161.dir/using-cheatR.html 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/saved_source_markers: -------------------------------------------------------------------------------- 1 | {"active_set":"","sets":[]} -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/54F4CD67: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "14,0", 3 | "scrollLine" : "0" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/5F2748F1: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "9,69", 3 | "scrollLine" : "0" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/610E5171: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "77,2", 3 | "docOutlineVisible" : "1", 4 | "scrollLine" : "66" 5 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/79F4A964: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "48,73", 3 | "scrollLine" : "50" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/820C4FC0: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "55,0", 3 | "docOutlineVisible" : "1", 4 | "scrollLine" : "54" 5 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/83D3127E: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "32,20", 3 | "scrollLine" : "24" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/9AAD83DA: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "49,2", 3 | "docOutlineSize" : "163.86738044363184", 4 | "docOutlineVisible" : "1", 5 | "scrollLine" : "31" 6 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/AFA24831: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "24,43", 3 | "last_setup_crc32" : "729288DD78b0692", 4 | "scrollLine" : "0" 5 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/B005AC79: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "8,3", 3 | "scrollLine" : "0" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/E7798880: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "3,16", 3 | "scrollLine" : "0" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/E7DD2F97: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "11,46", 3 | "scrollLine" : "3" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/EB65C34A: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /.Rproj.user/1B21F6B0/sources/prop/INDEX: -------------------------------------------------------------------------------- 1 | C%3A%2FUsers%2FUSER%2FDownloads%2FcheatR%2FR%2Fhello.R="EB65C34A" 2 | ~%2FR%2FbayestestR%2Ftests%2Ftestthat%2Ftest-as.data.frame.density.R="33999306" 3 | ~%2FR%2FcheatR%2FDESCRIPTION="E7798880" 4 | ~%2FR%2FcheatR%2FNAMESPACE="54F4CD67" 5 | ~%2FR%2FcheatR%2FNEWS.md="5F2748F1" 6 | ~%2FR%2FcheatR%2FR%2Fcatch_em.R="610E5171" 7 | ~%2FR%2FcheatR%2FR%2Fmethods.R="9AAD83DA" 8 | ~%2FR%2FcheatR%2FR%2Fshiny.R="820C4FC0" 9 | ~%2FR%2FcheatR%2FREADME.Rmd="AFA24831" 10 | ~%2FR%2FcheatR%2Fman%2Fcompare_txt.Rd="E7DD2F97" 11 | ~%2FR%2FcheatR%2Ftests%2Ftestthat%2Ftest-chatch_em.R="D049853" 12 | ~%2FR%2FcheatR%2Ftests%2Ftestthat.R="ACB9B0B8" 13 | ~%2FR%2FcheatR%2Fvignettes%2F.gitignore="A4BE38B5" 14 | ~%2FR%2FcheatR%2Fvignettes%2Fmy-vignette.Rmd="83D3127E" 15 | ~%2FR%2FcheatR%2Fvignettes%2Fusing-cheatR.Rmd="79F4A964" 16 | ~%2FR%2FmsbmiscR%2Fknitr%2Fmulti%20knit%2Freport.Rmd="B005AC79" 17 | -------------------------------------------------------------------------------- /.Rproj.user/shared/notebooks/175BACE8-README/1/s/chunks.json: -------------------------------------------------------------------------------- 1 | {"chunk_definitions":[],"doc_write_time":1588172834} -------------------------------------------------------------------------------- /.Rproj.user/shared/notebooks/3531CF06-using-cheatR/1/s/chunks.json: -------------------------------------------------------------------------------- 1 | {"chunk_definitions":[{"chunk_id":"ciz8q3hfot9ps","chunk_label":"unnamed-chunk-1","document_id":"558F24BC","expansion_state":0,"options":{"engine":"r","include":false,"label":"unnamed-chunk-1"},"row":17,"row_count":1,"visible":true},{"chunk_id":"cr3idk8t8fncx","chunk_label":"unnamed-chunk-2","document_id":"B93A58A9","expansion_state":0,"options":{"engine":"r","label":"unnamed-chunk-3"},"row":26,"row_count":1,"visible":true},{"chunk_id":"c8thw8mqsbek1","chunk_label":"unnamed-chunk-3","document_id":"36EFCB41","expansion_state":0,"options":{"engine":"r","label":"unnamed-chunk-4"},"row":41,"row_count":1,"visible":true},{"chunk_id":"c84p4o083dnf9","chunk_label":"unnamed-chunk-4","document_id":"36EFCB41","expansion_state":0,"options":{"engine":"r","label":"unnamed-chunk-6"},"row":47,"row_count":1,"visible":true},{"chunk_id":"chco7yp48qrmj","chunk_label":"cheater_graph","document_id":"36EFCB41","expansion_state":0,"options":{"engine":"r","label":"cheater_graph"},"row":53,"row_count":1,"visible":true}],"chunk_rendered_width":700,"doc_write_time":1587801609} -------------------------------------------------------------------------------- /.Rproj.user/shared/notebooks/patch-chunk-names: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/.Rproj.user/shared/notebooks/patch-chunk-names -------------------------------------------------------------------------------- /.Rproj.user/shared/notebooks/paths: -------------------------------------------------------------------------------- 1 | C:/Users/USER/Documents/R/MSBMisc/vignettes/MSBMisc.Rmd="91D5D8C4" 2 | C:/Users/USER/Documents/R/cheatR/DESCRIPTION="4A9E981B" 3 | C:/Users/USER/Documents/R/cheatR/NAMESPACE="87D3CEBE" 4 | C:/Users/USER/Documents/R/cheatR/NEWS.md="26F28B44" 5 | C:/Users/USER/Documents/R/cheatR/R/catch_em.R="FCA7695" 6 | C:/Users/USER/Documents/R/cheatR/R/methods.R="35C17A43" 7 | C:/Users/USER/Documents/R/cheatR/R/onload.R="2EEE74EB" 8 | C:/Users/USER/Documents/R/cheatR/R/shiny.R="3B0B0922" 9 | C:/Users/USER/Documents/R/cheatR/README.Rmd="175BACE8" 10 | C:/Users/USER/Documents/R/cheatR/_pkgdown.yml="4409C6DB" 11 | C:/Users/USER/Documents/R/cheatR/man/compare_txt.Rd="7EB26283" 12 | C:/Users/USER/Documents/R/cheatR/tests/testthat.R="87910459" 13 | C:/Users/USER/Documents/R/cheatR/tests/testthat/test-chatch_em.R="D96FF812" 14 | C:/Users/USER/Documents/R/cheatR/vignettes/.gitignore="F6B4471" 15 | C:/Users/USER/Documents/R/cheatR/vignettes/using-cheatR.R="91E27B5D" 16 | C:/Users/USER/Documents/R/cheatR/vignettes/using-cheatR.Rmd="3531CF06" 17 | C:/Users/USER/Documents/R/easystats/effectsize/R/convert_tFz_to_d.R="644D0BDA" 18 | C:/Users/USER/Documents/R/easystats/effectsize/README.Rmd="430DE2EE" 19 | C:/Users/USER/Documents/R/easystats/effectsize/vignettes/from_test_statistics.Rmd="4DD152A8" 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-check.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | name: R-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: windows-latest, r: 'devel'} 22 | - {os: macOS-latest, r: 'devel'} 23 | - {os: ubuntu-18.04, r: 'devel'} 24 | - {os: windows-latest, r: 'release'} 25 | - {os: macOS-latest, r: 'release'} 26 | - {os: ubuntu-18.04, r: 'release'} 27 | - {os: windows-latest, r: 'oldrel'} 28 | - {os: macOS-latest, r: 'oldrel'} 29 | - {os: ubuntu-18.04, r: 'oldrel'} 30 | # Older still 31 | - {os: ubuntu-18.04, r: '4.0.0'} 32 | 33 | env: 34 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 35 | RSPM: ${{ matrix.config.rspm }} 36 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | steps: 39 | - uses: actions/checkout@v2 40 | 41 | - uses: r-lib/actions/setup-r@master 42 | with: 43 | r-version: ${{ matrix.config.r }} 44 | 45 | - uses: r-lib/actions/setup-pandoc@master 46 | 47 | - name: Query dependencies 48 | run: | 49 | install.packages('remotes') 50 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 51 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 52 | shell: Rscript {0} 53 | 54 | - name: Cache R packages 55 | if: runner.os != 'Windows' 56 | uses: actions/cache@v1 57 | with: 58 | path: ${{ env.R_LIBS_USER }} 59 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 60 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 61 | 62 | - name: Install system dependencies 63 | if: runner.os == 'Linux' 64 | env: 65 | RHUB_PLATFORM: linux-x86_64-ubuntu-gcc 66 | run: | 67 | Rscript -e "remotes::install_github('r-hub/sysreqs')" 68 | sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") 69 | sudo -s eval "$sysreqs" 70 | - name: Install dependencies 71 | run: | 72 | remotes::install_deps(dependencies = TRUE) 73 | remotes::install_cran("rcmdcheck") 74 | shell: Rscript {0} 75 | 76 | - name: Session info 77 | run: | 78 | options(width = 100) 79 | pkgs <- installed.packages()[, "Package"] 80 | sessioninfo::session_info(pkgs, include_base = TRUE) 81 | shell: Rscript {0} 82 | 83 | - name: Check 84 | env: 85 | _R_CHECK_CRAN_INCOMING_: false 86 | _R_CHECK_FORCE_SUGGESTS_: false 87 | run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") 88 | shell: Rscript {0} 89 | 90 | - name: Show testthat output 91 | if: always() 92 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true 93 | shell: bash 94 | 95 | - name: Upload check results 96 | if: failure() 97 | uses: actions/upload-artifact@master 98 | with: 99 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 100 | path: check 101 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: main 4 | 5 | name: pkgdown 6 | 7 | jobs: 8 | pkgdown: 9 | runs-on: macOS-latest 10 | env: 11 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - uses: r-lib/actions/setup-r@master 16 | 17 | - uses: r-lib/actions/setup-pandoc@master 18 | 19 | - uses: r-lib/actions/setup-tinytex@v1 20 | 21 | - name: Query dependencies 22 | run: | 23 | install.packages('remotes') 24 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 25 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 26 | shell: Rscript {0} 27 | 28 | - name: Cache R packages 29 | uses: actions/cache@v1 30 | with: 31 | path: ${{ env.R_LIBS_USER }} 32 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 33 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 34 | 35 | - name: Install dependencies 36 | run: | 37 | install.packages("remotes") 38 | # To get links for homepage 39 | remotes::install_cran("devtools") 40 | remotes::install_deps(dependencies = TRUE) 41 | remotes::install_dev("pkgdown") 42 | shell: Rscript {0} 43 | 44 | - name: Install package 45 | run: R CMD INSTALL . 46 | 47 | - name: Deploy package 48 | run: | 49 | git config --local user.email "actions@github.com" 50 | git config --local user.name "GitHub Actions" 51 | Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' 52 | -------------------------------------------------------------------------------- /.github/workflows/render-readme.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: main 4 | 5 | name: Render README 6 | 7 | jobs: 8 | render: 9 | name: Render README 10 | runs-on: macOS-latest 11 | env: 12 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: r-lib/actions/setup-r@v1 16 | - uses: r-lib/actions/setup-pandoc@v1 17 | - name: Install rmarkdown, remotes, and the local package 18 | run: | 19 | install.packages("remotes") 20 | remotes::install_local(".") 21 | remotes::install_cran("rmarkdown") 22 | shell: Rscript {0} 23 | - name: Render README 24 | run: Rscript -e 'rmarkdown::render("README.Rmd")' 25 | - name: Commit results 26 | run: | 27 | git config --local user.email "actions@github.com" 28 | git config --local user.name "GitHub Actions" 29 | git commit README.md -m 'Re-build README.Rmd' || echo "No changes to commit" 30 | git push origin || echo "No changes to commit" 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # Example code in package build process 9 | *-Ex.R 10 | 11 | # Output files from R CMD build 12 | /*.tar.gz 13 | 14 | # Output files from R CMD check 15 | /*.Rcheck/ 16 | 17 | # RStudio files 18 | .Rproj.user/ 19 | 20 | # produced vignettes 21 | vignettes/*.html 22 | vignettes/*.pdf 23 | 24 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 25 | .httr-oauth 26 | 27 | # knitr and R markdown default cache directories 28 | /*_cache/ 29 | /cache/ 30 | 31 | # Temporary files created by R markdown 32 | *.utf8.md 33 | *.knit.md 34 | 35 | # Shiny token, see https://shiny.rstudio.com/articles/shinyapps.html 36 | rsconnect/ 37 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: cheatR 2 | Type: Package 3 | Title: Catch Cheaters 4 | Description: A set of functions to compare texts for similarity, and plot a graph of similarities among the compared texts. These functions were originally developed for detection of overlap in course hand-in. 5 | Version: 1.2.1-1 6 | Authors@R: c( 7 | person("Mattan S.", 8 | "Ben-Shachar", 9 | role = c("aut", "cre"), 10 | email = "matanshm@post.bgu.ac.il", 11 | comment = c(ORCID = "0000-0002-4287-4801")), 12 | person("Almog", 13 | "Simchon", 14 | role = c("aut")) 15 | ) 16 | Maintainer: Mattan S. Ben-Shachar 17 | URL: https://mattansb.github.io/cheatR/ 18 | BugReports: https://github.com/mattansb/cheatR/issues/ 19 | Depends: R (>= 4.0.0) 20 | Imports: textreadr, ngram, purrr, utils, R.utils 21 | Suggests: 22 | knitr, 23 | rmarkdown, 24 | testthat, 25 | devtools, 26 | shiny, 27 | DT, 28 | ggplot2, 29 | tidygraph, 30 | ggraph, 31 | grid 32 | License: GPL-3 33 | Encoding: UTF-8 34 | LazyData: true 35 | RoxygenNote: 7.1.2 36 | Roxygen: list(markdown = TRUE) 37 | VignetteBuilder: knitr 38 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(hist,chtrs) 4 | S3method(plot,chtrs) 5 | S3method(print,chtrs) 6 | S3method(summary,chtrs) 7 | export(catch_em) 8 | export(catch_em_app) 9 | export(compare_txt) 10 | import(purrr) 11 | importFrom(R.utils,withTimeout) 12 | importFrom(ngram,get.phrasetable) 13 | importFrom(ngram,ngram) 14 | importFrom(textreadr,read_document) 15 | importFrom(utils,packageVersion) 16 | importFrom(utils,setTxtProgressBar) 17 | importFrom(utils,txtProgressBar) 18 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # cheatR NEWS 2 | 3 | This doc details user-facing changes only. 4 | 5 | ## Version 1.2.1 6 | 7 | - `pkgdown` site 8 | 9 | ## Version 1.2.0 10 | 11 | ### Breaking Changes 12 | 13 | - `catch_em()` now returns a matrix, and not a list. 14 | - Plotting now removes lonely nodes (nodes that are not related to any other node). This can be changed by setting `remove_lonely = FALSE`. 15 | - New `compare_txt()` for comparing pairs of texts. 16 | 17 | ## Version 1.0.0-5 18 | 19 | | Function | Update | Notes | 20 | |---------:|:-------|:------| 21 | |`graph_em`| NEW | Plots a graph between the similarity scores. | 22 | | `print`, `summary`, `hist` | NEW | New methods for class `chtrs`. | 23 | 24 | ## Version 1.0.0 25 | 26 | | Function | Update | Notes | 27 | |---------:|:-------|:------| 28 | |`catch_em`| IMPROVEMENT | Function now accounts for auto-similarities between docs, making similarity estimates more precise (previous estimates where slightly skewed upwards) | -------------------------------------------------------------------------------- /R/catch_em.R: -------------------------------------------------------------------------------- 1 | #' Match cheaters 2 | #' 3 | #' @author Mattan S. Ben-Shachar 4 | #' @param flist a list of documents (`.doc`/`.docx`/`.pdf`). A full/relative 5 | #' path must be provided. 6 | #' @param n_grams see [`ngram`] package. 7 | #' @param time_lim max time in seconds for each comparison. Defult is 1 second, 8 | #' had no problem comparing documents with 50K words. 9 | #' @param progress_bar Should a progress bar be printed to the console? 10 | #' 11 | #' @return A correlation matrix of class `chtrs` with each cell indicating the match (0-1) between two of the documents. 12 | #' 13 | #' @examples 14 | #' if (interactive()) { 15 | #' files <- choose.files() 16 | #' catch_em(files) 17 | #' } 18 | #' 19 | #' @import purrr 20 | #' @importFrom utils txtProgressBar 21 | #' @importFrom utils setTxtProgressBar 22 | #' @importFrom R.utils withTimeout 23 | #' @importFrom textreadr read_document 24 | #' @export 25 | catch_em <- function(flist, n_grams = 10, time_lim = 1L, progress_bar = TRUE){ 26 | if (isTRUE(length(flist) < 2)) { 27 | stop("Must specify at least 2 files.") 28 | } 29 | 30 | # load txt and mark bad files 31 | message('Reading documents...') 32 | safe_read <- safely(textreadr::read_document) 33 | txt_all <- map(flist, ~ safe_read(.x, combine = TRUE)$result) 34 | txt_all <- flatten_chr(map_if(txt_all, is_empty, ~ NA_character_)) 35 | bad_files_to_read <- flist[is.na(txt_all)] 36 | flist <- flist[!is.na(txt_all)] 37 | txt_all <- txt_all[!is.na(txt_all)] 38 | 39 | if (isTRUE(length(txt_all)==0)) { 40 | stop("Couldn't read any files:\n", paste0(bad_files_to_read, collapse = ",\t"), 41 | call. = FALSE) 42 | } 43 | message('\b Done!') 44 | 45 | # pre-alocate 46 | res <- matrix(NA, 47 | nrow = length(flist), 48 | ncol = length(flist)) 49 | diag(res) <- 1 50 | colnames(res) <- rownames(res) <- basename(flist) 51 | 52 | bad_files <- matrix(character(), ncol = 2) 53 | 54 | message('Looking for cheaters...') 55 | if(isTRUE(progress_bar)) pb <- utils::txtProgressBar(min = 0, max = max(c(length(flist), 1))) 56 | for (i in seq_along(flist)) { 57 | for (j in seq_along(flist)) { 58 | if (isTRUE(is.na(res[j, i]))) { 59 | results <- R.utils::withTimeout({ 60 | compare_txt(txt_all[i], txt_all[j], n_grams = n_grams) 61 | }, 62 | timeout = time_lim, 63 | onTimeout = "silent") 64 | if (isTRUE(is.null(results))) { 65 | bad_files <- rbind(bad_files, 66 | c(flist[i], flist[j])) 67 | } 68 | res[j, i] <- res[i, j] <- results 69 | } 70 | 71 | # progbar 72 | if(isTRUE(progress_bar)) utils::setTxtProgressBar(pb, i) 73 | } 74 | } 75 | 76 | res[upper.tri(res)] <- NA 77 | 78 | attributes(res) <- c(attributes(res), bad_read = bad_files_to_read, bad_ngrams = bad_files) 79 | class(res) <- c('chtrs', class(res)) 80 | message('\nBusted!') 81 | return(res) 82 | } 83 | 84 | #' Match cheaters 85 | #' 86 | #' @author Mattan S. Ben-Shachar 87 | #' @param txt1,txt2 character vectors to compare, each of length 1. 88 | #' @param n_grams see [ngram] package. 89 | #' @param across How should the percentage of overlap be computed? 90 | #' 91 | #' @return The percent (0-1) of overlap between the texts 92 | #' 93 | #' @examples 94 | #' text1 <- "My horse is large and white, and I ride it every day." 95 | #' text2 <- "My mule is large and brown, and I ride it most days." 96 | #' compare_txt(text1, text2, n_grams = 3) 97 | #' 98 | #' @import purrr 99 | #' @importFrom ngram ngram 100 | #' @importFrom ngram get.phrasetable 101 | #' @export 102 | compare_txt <- function(txt1,txt2, n_grams = 10, 103 | across = c("both","txt1","txt2")) { 104 | across <- match.arg(across) 105 | 106 | if (isTRUE(is.null(txt1)) || isTRUE(is.null(txt2))) { 107 | return(NULL) 108 | } 109 | 110 | total_freq <- function(x) { 111 | x$tot <- sum(x$freq) 112 | return(x) 113 | } 114 | 115 | txts <- list(txt1, txt2) 116 | temp_grams <- map(txts, ngram::ngram, n = n_grams) 117 | temp_phrs <- map(temp_grams, ngram::get.phrasetable) 118 | temp_phrs <- map(temp_phrs, total_freq) 119 | XX <- merge(temp_phrs[[1]], temp_phrs[[2]], by = 'ngrams') 120 | if (isTRUE(nrow(XX) == 0)) return(0) 121 | XX$freq <- 2 * pmin(XX$freq.x, XX$freq.y, na.rm = TRUE) 122 | 123 | switch (across, 124 | both = sum(XX$freq) / (XX$tot.x[1] + XX$tot.y[1]), 125 | txt1 = sum(XX$freq.x) / XX$tot.x[1], 126 | txt2 = sum(XX$freq.y) / XX$tot.y[1] 127 | ) 128 | } 129 | -------------------------------------------------------------------------------- /R/methods.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | print.chtrs <- function(x, digits = 0, ...) { 3 | x_ <- x 4 | 5 | add_na <- is.na(x[]) 6 | x[] <- paste0(100 * round(x[], 2 + digits), "%") 7 | x[add_na] <- NA 8 | x[upper.tri(x)] <- '' 9 | 10 | bad_read <- length(attr(x,"bad_read")) 11 | bad_ngrams <- nrow(attr(x,"bad_ngrams")) 12 | 13 | x <- unclass(x) 14 | attr(x, "bad_read") <- NULL 15 | attr(x, "bad_ngrams") <- NULL 16 | 17 | print(unclass(x), quote = F, right = T) 18 | if (isTRUE(bad_read > 0)) { 19 | cat('\n', bad_read, ' files could not be read.') 20 | } else { 21 | cat('\nAll files read successfully.') 22 | } 23 | 24 | if (isTRUE(!is.null(bad_ngrams))) { 25 | cat('\n', bad_read, ' comparisons failed.') 26 | } else { 27 | cat('\nAll files compared successfully.') 28 | } 29 | 30 | invisible(x_) 31 | } 32 | 33 | #' Summarise Cheatrs 34 | #' 35 | #' @author Mattan S. Ben-Shachar 36 | #' @param object output of [catch_em()]. 37 | #' @param bad_files logical. Instead of the result matrix, should return instead 38 | #' the list of bad files (that did not compare / load)? Defaults to `FALSE`. 39 | #' @param ... Not used. 40 | #' 41 | #' @return The input `chtrs` matrix, or a list of bad files (when `bad_files = TRUE`). 42 | #' 43 | #' @examples 44 | #' 45 | #' if (interactive()) { 46 | #' files <- choose.files() 47 | #' res <- catch_em(files) 48 | #' 49 | #' summary(res, bad_files = TRUE) 50 | #' } 51 | #' 52 | #' @export 53 | summary.chtrs <- function(object, bad_files = FALSE, ...) { 54 | if (isTRUE(bad_files)) { 55 | list( 56 | bad_read = attr(object, "bad_read"), 57 | bad_ngrams = attr(object, "bad_ngrams") 58 | ) 59 | } else { 60 | object 61 | } 62 | } 63 | 64 | #' Plot cheatrs / histogram of similarity scores 65 | #' 66 | #' Requires `ggraph` and `ggplot2` to work. 67 | #' 68 | #' @author Mattan S. Ben-Shachar 69 | #' 70 | #' @param x output of [catch_em()]. 71 | #' @param weight_range range of edge values to plot 72 | #' @param ... passed to [ggraph::ggraph()] or [ggplot2::geom_histogram]. 73 | #' @param remove_lonely should lonely nodes (not connected to any edges) be 74 | #' removed from the graph? 75 | #' @param digits Number of digits to round the percentage to. 76 | #' 77 | #' @return A `ggplot2` plot. 78 | #' 79 | #' @examples 80 | #' if (interactive()) { 81 | #' files <- choose.files() 82 | #' res <- catch_em(files) 83 | #' 84 | #' plot(res) 85 | #' hist(res) 86 | #' } 87 | #' 88 | #' @export 89 | plot.chtrs <- function(x, 90 | weight_range = c(.4,1), 91 | remove_lonely = TRUE, 92 | digits = 0, ...){ 93 | # dumb workaround 94 | .data <- edges <- nodes <- NULL 95 | 96 | if(isTRUE(!requireNamespace("tidygraph"))) 97 | stop("This function requares 'tidygraph' to work. Please install it.") 98 | if (isTRUE(!requireNamespace("ggraph"))) 99 | stop("This function requares 'ggraph' to work. Please install it.") 100 | if (isTRUE(!requireNamespace("ggplot2"))) 101 | stop("This function requares 'ggplot2' to work. Please install it.") 102 | 103 | if (isTRUE(dim(x)[1] < 3)) { 104 | stop("Cannot plot a graph between only 2 documents.", call. = FALSE) 105 | } 106 | 107 | `%>%` <- tidygraph::`%>%` 108 | `%E>%` <- tidygraph::`%E>%` 109 | `%N>%` <- tidygraph::`%N>%` 110 | 111 | results_graph <- x %>% 112 | tidygraph::as_tbl_graph() %E>% 113 | tidygraph::filter(!is.na(.data$weight), 114 | .data$weight >= weight_range[1], 115 | .data$weight <= weight_range[2]) 116 | 117 | if (isTRUE(remove_lonely)) { 118 | results_graph <- results_graph %E>% 119 | tidygraph::filter(.data$from != .data$to) %N>% 120 | tidygraph::filter( 121 | 1:tidygraph::n() %in% 122 | c(tidygraph::.E()$from, tidygraph::.E()$to) 123 | ) 124 | } 125 | 126 | if (isTRUE(nrow(tidygraph::as_tibble(tidygraph::activate(results_graph, nodes))) == 0) || 127 | isTRUE(nrow(tidygraph::as_tibble(tidygraph::activate(results_graph, edges))) == 0)) { 128 | stop("Cannot plot a graph without nodes/edges. Try changing 'weight_range'.", 129 | call. = FALSE) 130 | } 131 | 132 | ggraph::ggraph(results_graph, ...) + 133 | ggraph::geom_edge_fan( 134 | ggplot2::aes(label = paste0(100 * round(.data$weight, 2 + digits), "%")), 135 | angle_calc = 'along', 136 | label_dodge = grid::unit(2.5, 'mm') 137 | ) + 138 | ggraph::geom_node_label(ggplot2::aes(label = .data$name)) 139 | } 140 | 141 | 142 | #' @export 143 | #' @rdname plot.chtrs 144 | hist.chtrs <- function(x, ...) { 145 | if (isTRUE(!requireNamespace("ggplot2"))) 146 | stop("This function requares 'ggplot2' to work. Please install it.") 147 | 148 | x <- x[lower.tri(x)] 149 | 150 | ggplot2::ggplot() + 151 | ggplot2::geom_histogram(ggplot2::aes(x = x), ...) + 152 | ggplot2::labs(main = 'Histogram of similarity scores', 153 | x = 'Similarity') 154 | } 155 | -------------------------------------------------------------------------------- /R/onload.R: -------------------------------------------------------------------------------- 1 | .onAttach <- function(...){ 2 | packageStartupMessage( 3 | "Catch 'em cheaters!" 4 | ) 5 | } -------------------------------------------------------------------------------- /R/shiny.R: -------------------------------------------------------------------------------- 1 | #' Run `catch_em()` with `shiny` 2 | #' 3 | #' Run `catch_em()` interactively. 4 | #' 5 | #' @param ... Not used. 6 | #' 7 | #' @author Almog Simchon 8 | #' 9 | #' @return A `shiny` app object. 10 | #' 11 | #' @examples 12 | #' 13 | #' if (interactive()) { 14 | #' catch_em_app() 15 | #' } 16 | #' 17 | #' @importFrom utils packageVersion 18 | #' @export 19 | catch_em_app <- function(...) { 20 | if (isTRUE(!requireNamespace("shiny"))) 21 | stop("This function requares 'shiny' to work. Please install it.") 22 | if (isTRUE(!requireNamespace("DT"))) 23 | stop("This function requares 'DT' to work. Please install it.") 24 | if (isTRUE(!requireNamespace("ggplot2"))) 25 | stop("This function requares 'ggplot2' to work. Please install it.") 26 | 27 | # Run the application 28 | shiny::shinyApp(ui = ui_gce(), server = server_gce) 29 | } 30 | 31 | ui_gce <- function() { 32 | shiny::fluidPage( 33 | # Application title 34 | shiny::titlePanel(paste0( 35 | "Gotta Catch 'em All (v", utils::packageVersion("cheatR"), ')' 36 | )), 37 | 38 | # Sidebar with a slider input for number of bins 39 | shiny::sidebarLayout( 40 | shiny::sidebarPanel( 41 | # img(src=paste0(dirname(rstudioapi::getSourceEditorContext()$path), "cheatrball.png"), 42 | # align = "right", width="20%"), 43 | 44 | shiny::h3("Selected Documents"), 45 | 46 | shiny::fileInput("input_doc_list", "Select Documents Files", 47 | multiple = TRUE), 48 | 49 | shiny::tableOutput("output_doc_list"), 50 | 51 | shiny::numericInput( 52 | 'n_grams', 53 | "n-grams (change only if you know what you're doing!)", 54 | value = 10, 55 | min = 2 56 | ), 57 | 58 | shiny::sliderInput( 59 | "weight_range", 60 | "Similarity coeffs to plot", 61 | min = 0, 62 | max = 1, 63 | value = c(0.4, 1) 64 | ), 65 | 66 | shiny::checkboxInput("lonely", "Remove lonely files?", value = TRUE), 67 | 68 | shiny::tags$div( 69 | class = "header", 70 | checked = NA, 71 | 72 | list( 73 | shiny::HTML("Want more info and Pokemon references?"), 74 | shiny::tags$a(href = "https://github.com/mattansb/cheatR", "over here") 75 | ) 76 | ) 77 | ), 78 | 79 | # Show a plot of the generated distribution 80 | shiny::mainPanel( 81 | shiny::h3("Results"), 82 | 83 | DT::dataTableOutput("output_doc_matrix", width = "80%"), 84 | 85 | shiny::plotOutput('output_graph') 86 | ) 87 | ) 88 | ) 89 | } 90 | 91 | server_gce <- function(input, output) { 92 | catch_results <- shiny::reactive({ 93 | if (isTRUE(is.null(input$input_doc_list))) 94 | return(NA) 95 | 96 | res <- suppressMessages( 97 | catch_em(input$input_doc_list$datapath, 98 | n_grams = input$n_grams, 99 | progress_bar = FALSE) 100 | ) 101 | 102 | 103 | colnames(res) <- 104 | rownames(res) <- 105 | basename(input$input_doc_list$name) 106 | return(res) 107 | }) 108 | 109 | output$output_doc_list <- shiny::renderTable({ 110 | if (isTRUE(is.null(input$input_doc_list))) 111 | return(data.frame()) 112 | 113 | data.frame(Document = input$input_doc_list$name) 114 | }) 115 | 116 | output$output_doc_matrix <- DT::renderDataTable({ 117 | if (isTRUE(is.na(catch_results()[1]))) 118 | return(data.frame()) 119 | 120 | round(catch_results(), 3) 121 | }, 122 | rownames = TRUE, 123 | extensions = 'Buttons', 124 | options = list( 125 | dom = 'Bfrtip', 126 | buttons = c('copy', 'csv', 'excel', 'pdf', 'print') 127 | )) 128 | 129 | 130 | output$output_graph <- shiny::renderPlot({ 131 | if (isTRUE(is.na(catch_results()[1])) || isTRUE(nrow(catch_results()) < 3)) 132 | return(ggplot2::ggplot() + ggplot2::theme_void()) 133 | 134 | plot.chtrs( 135 | catch_results(), 136 | weight_range = input$weight_range, 137 | remove_lonely = input$lonely 138 | ) + 139 | ggplot2::theme_void() 140 | }) 141 | } 142 | 143 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | ```{r setup, include=FALSE} 6 | knitr::opts_chunk$set( 7 | echo = TRUE 8 | , fig.path = "/man/figures/" 9 | ) 10 | options(knitr.kable.NA = '', digits = 3) 11 | library(ggplot2) 12 | theme_set(theme_void()) 13 | ``` 14 | 15 | 16 | 17 | # cheatR: Catch 'em baddies 18 | 19 | This is a mini package to help you find cheaters by comparing hand-ins! 20 | ([Read more](https://shouldbewriting.netlify.app/posts/2018-07-29-cheatr/) about the circumstances that brought about the development of this package.) 21 | 22 | ## Download and Install 23 | 24 | [![CRAN](http://www.r-pkg.org/badges/version/cheatR)](https://cran.r-project.org/package=cheatR) 25 | [![downloads](http://cranlogs.r-pkg.org/badges/cheatR)](https://cran.r-project.org/package=cheatR) 26 | 27 | You can install `cheatR` with: 28 | 29 | ```{r, eval=FALSE} 30 | install.packages("cheatR") 31 | ``` 32 | 33 | Or get the dev version from [github](https://github.com/mattansb/cheatR): 34 | 35 | ```{r, eval=FALSE} 36 | # install.packages("remotes") 37 | remotes::install_github("mattansb/cheatR") 38 | ``` 39 | 40 | See the [*using `cheatR`*](https://mattansb.github.io/cheatR/articles/using-cheatR.html) vignette, or use the accompanying `Shiny` app that can either be run locally 41 | 42 | ```{r, eval=FALSE} 43 | cheatR::catch_em_app() 44 | ``` 45 | 46 | Or can be found on [shinyapps.io](https://almogsi.shinyapps.io/cheatR/)! 47 | 48 | 49 | 50 | ## Limitations? 51 | 52 | - As far as we can tell, this should work on any language; we tried both English and Hebrew, with and without setting `Sys.setlocale("LC_ALL", "Hebrew")`. 53 | - Best performance was achieved on `R` version > 3.5.0. 54 | 55 | ## Authors 56 | 57 | - **Mattan S. Ben-Shachar** [aut, cre]. 58 | - **Almog Simchon** [aut, cre]. 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # cheatR: Catch ’em baddies 5 | 6 | This is a mini package to help you find cheaters by comparing 7 | hand-ins! 8 | ([Read 9 | more](https://shouldbewriting.netlify.app/posts/2018-07-29-cheatr/) 10 | about the circumstances that brought about the development of this 11 | package.) 12 | 13 | ## Download and Install 14 | 15 | [![CRAN](http://www.r-pkg.org/badges/version/cheatR)](https://cran.r-project.org/package=cheatR) 16 | [![downloads](http://cranlogs.r-pkg.org/badges/cheatR)](https://cran.r-project.org/package=cheatR) 17 | 18 | You can install `cheatR` with: 19 | 20 | ``` r 21 | install.packages("cheatR") 22 | ``` 23 | 24 | Or get the dev version from 25 | [github](https://github.com/mattansb/cheatR): 26 | 27 | ``` r 28 | # install.packages("remotes") 29 | remotes::install_github("mattansb/cheatR") 30 | ``` 31 | 32 | See the [*using 33 | `cheatR`*](https://mattansb.github.io/cheatR/articles/using-cheatR.html) 34 | vignette, or use the accompanying `Shiny` app that can either be run 35 | locally 36 | 37 | ``` r 38 | cheatR::catch_em_app() 39 | ``` 40 | 41 | Or can be found on [shinyapps.io](https://almogsi.shinyapps.io/cheatR/)! 42 | 43 | 44 | 45 | ## Limitations? 46 | 47 | - As far as we can tell, this should work on any language; we tried 48 | both English and Hebrew, with and without setting 49 | `Sys.setlocale("LC_ALL", "Hebrew")`. 50 | - Best performance was achieved on `R` version \> 3.5.0. 51 | 52 | ## Authors 53 | 54 | - **Mattan S. Ben-Shachar** \[aut, cre\]. 55 | - **Almog Simchon** \[aut, cre\]. 56 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | reference: 2 | - title: "Main Functions" 3 | contents: 4 | - catch_em 5 | - catch_em_app 6 | - compare_txt 7 | 8 | - title: "Plot and Summary" 9 | contents: 10 | - plot.chtrs 11 | - summary.chtrs 12 | 13 | -------------------------------------------------------------------------------- /cheatR.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: iso-8859-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | StripTrailingWhitespace: Yes 16 | 17 | BuildType: Package 18 | PackageUseDevtools: Yes 19 | PackageInstallArgs: --no-multiarch --with-keep.source 20 | PackageRoxygenize: rd,collate,namespace,vignette 21 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Page not found (404) • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 |
63 | 117 | 118 | 119 | 120 |
121 | 122 |
123 |
124 | 127 | 128 | Content not found. Please use links in the navbar. 129 | 130 |
131 | 132 | 137 | 138 |
139 | 140 | 141 | 142 |
143 | 146 | 147 |
148 |

Site built with pkgdown 1.5.1.

149 |
150 | 151 |
152 |
153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /docs/articles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Articles • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 |
63 | 117 | 118 | 119 | 120 |
121 | 122 |
123 |
124 | 127 | 128 |
129 |

All vignettes

130 |

131 | 132 |
133 |
Using `cheatR`
134 |
135 |
136 |
137 |
138 |
139 | 140 | 141 |
142 | 145 | 146 |
147 |

Site built with pkgdown 1.5.1.

148 |
149 | 150 |
151 |
152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /docs/articles/using-cheatR.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Using `cheatR` • cheatR 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 |
23 |
78 | 79 | 80 | 81 | 82 |
83 |
84 | 95 | 96 | 97 | 98 |
99 |

100 | Scripting

101 |

Create a list of files:

102 |
my_files <- list.files(path = '../man/files/', pattern = '.doc', full.names = TRUE)
103 | my_files
104 | #> [1] "../man/files/paper1_copy1.docx" "../man/files/paper1_copy2.docx"
105 | #> [3] "../man/files/paper1_copy3.docx" "../man/files/paper2_copy1.docx"
106 |

The first 3 documents are different drafts of the same paper, so we would expect them to be similar to each other. The last document is a draft of a different paper, so it should be dissimilar to the first 3. All files are about 45K words long.

107 |

Now we can use cheatR to find duplicates.

108 |

The only function, catch_em, takes the following input arguments:

109 |
    110 |
  • 111 | flist - a list of documents (.doc/.docx/.pdf). A full/relative path must be provided.
  • 112 |
  • 113 | n_grams - see ngram package.
  • 114 |
  • 115 | time_lim - max time in seconds for each comparison (we found that some corrupt files run forever and crash R, so a time limit might be needed).
  • 116 |
117 |
library(cheatR)
118 | #> Catch 'em cheaters!
119 | results <- catch_em(flist = my_files,
120 |                     n_grams = 10, time_lim = 1) # defaults
121 | #> Reading documents...
122 | #>  Done!
123 | #> Looking for cheaters...
124 | #> ================================================================================
125 | #> 
126 | #> Busted!
127 |

The resulting list contains a matrix with the similarity values between each pair of documents:

128 |
results
129 | #>                   paper1_copy1.docx paper1_copy2.docx paper1_copy3.docx
130 | #> paper1_copy1.docx              100%                                    
131 | #> paper1_copy2.docx               87%              100%                  
132 | #> paper1_copy3.docx               90%               88%              100%
133 | #> paper2_copy1.docx                0%                0%                0%
134 | #>                   paper2_copy1.docx
135 | #> paper1_copy1.docx                  
136 | #> paper1_copy2.docx                  
137 | #> paper1_copy3.docx                  
138 | #> paper2_copy1.docx              100%
139 | #> 
140 | #> All files read successfully.
141 | #> All files compared successfully.
142 |

You can also plot the relational graph if you’d like to get a more clear picture of who copied from who.

143 |
plot(results, weight_range = c(0.7, 1), remove_lonely = FALSE)
144 | #> Loading required namespace: tidygraph
145 | #> Loading required namespace: ggraph
146 | #> Using `stress` as default layout
147 |

148 |
149 |
150 | 151 | 154 | 155 |
156 | 157 | 158 | 159 |
162 | 163 |
164 |

Site built with pkgdown 1.5.1.

165 |
166 | 167 |
168 |
169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /docs/articles/using-cheatR_files/figure-html/cheater_graph-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/docs/articles/using-cheatR_files/figure-html/cheater_graph-1.png -------------------------------------------------------------------------------- /docs/authors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Authors • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 |
63 | 117 | 118 | 119 | 120 |
121 | 122 |
123 |
124 | 127 | 128 |
    129 |
  • 130 |

    Mattan S. Ben-Shachar. Author, maintainer. 131 |

    132 |
  • 133 |
  • 134 |

    Almog Simchon. Author. 135 |

    136 |
  • 137 |
138 | 139 |
140 | 141 |
142 | 143 | 144 | 145 |
146 | 149 | 150 |
151 |

Site built with pkgdown 1.5.1.

152 |
153 | 154 |
155 |
156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | 6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ 7 | 8 | /* All levels of nav */ 9 | nav[data-toggle='toc'] .nav > li > a { 10 | display: block; 11 | padding: 4px 20px; 12 | font-size: 13px; 13 | font-weight: 500; 14 | color: #767676; 15 | } 16 | nav[data-toggle='toc'] .nav > li > a:hover, 17 | nav[data-toggle='toc'] .nav > li > a:focus { 18 | padding-left: 19px; 19 | color: #563d7c; 20 | text-decoration: none; 21 | background-color: transparent; 22 | border-left: 1px solid #563d7c; 23 | } 24 | nav[data-toggle='toc'] .nav > .active > a, 25 | nav[data-toggle='toc'] .nav > .active:hover > a, 26 | nav[data-toggle='toc'] .nav > .active:focus > a { 27 | padding-left: 18px; 28 | font-weight: bold; 29 | color: #563d7c; 30 | background-color: transparent; 31 | border-left: 2px solid #563d7c; 32 | } 33 | 34 | /* Nav: second level (shown on .active) */ 35 | nav[data-toggle='toc'] .nav .nav { 36 | display: none; /* Hide by default, but at >768px, show it */ 37 | padding-bottom: 10px; 38 | } 39 | nav[data-toggle='toc'] .nav .nav > li > a { 40 | padding-top: 1px; 41 | padding-bottom: 1px; 42 | padding-left: 30px; 43 | font-size: 12px; 44 | font-weight: normal; 45 | } 46 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 47 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 48 | padding-left: 29px; 49 | } 50 | nav[data-toggle='toc'] .nav .nav > .active > a, 51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 53 | padding-left: 28px; 54 | font-weight: 500; 55 | } 56 | 57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ 58 | nav[data-toggle='toc'] .nav > .active > ul { 59 | display: block; 60 | } 61 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | (function() { 6 | 'use strict'; 7 | 8 | window.Toc = { 9 | helpers: { 10 | // return all matching elements in the set, or their descendants 11 | findOrFilter: function($el, selector) { 12 | // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ 13 | // http://stackoverflow.com/a/12731439/358804 14 | var $descendants = $el.find(selector); 15 | return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])'); 16 | }, 17 | 18 | generateUniqueIdBase: function(el) { 19 | var text = $(el).text(); 20 | var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-'); 21 | return anchor || el.tagName.toLowerCase(); 22 | }, 23 | 24 | generateUniqueId: function(el) { 25 | var anchorBase = this.generateUniqueIdBase(el); 26 | for (var i = 0; ; i++) { 27 | var anchor = anchorBase; 28 | if (i > 0) { 29 | // add suffix 30 | anchor += '-' + i; 31 | } 32 | // check if ID already exists 33 | if (!document.getElementById(anchor)) { 34 | return anchor; 35 | } 36 | } 37 | }, 38 | 39 | generateAnchor: function(el) { 40 | if (el.id) { 41 | return el.id; 42 | } else { 43 | var anchor = this.generateUniqueId(el); 44 | el.id = anchor; 45 | return anchor; 46 | } 47 | }, 48 | 49 | createNavList: function() { 50 | return $(''); 51 | }, 52 | 53 | createChildNavList: function($parent) { 54 | var $childList = this.createNavList(); 55 | $parent.append($childList); 56 | return $childList; 57 | }, 58 | 59 | generateNavEl: function(anchor, text) { 60 | var $a = $(''); 61 | $a.attr('href', '#' + anchor); 62 | $a.text(text); 63 | var $li = $('
  • '); 64 | $li.append($a); 65 | return $li; 66 | }, 67 | 68 | generateNavItem: function(headingEl) { 69 | var anchor = this.generateAnchor(headingEl); 70 | var $heading = $(headingEl); 71 | var text = $heading.data('toc-text') || $heading.text(); 72 | return this.generateNavEl(anchor, text); 73 | }, 74 | 75 | // Find the first heading level (`

    `, then `

    `, etc.) that has more than one element. Defaults to 1 (for `

    `). 76 | getTopLevel: function($scope) { 77 | for (var i = 1; i <= 6; i++) { 78 | var $headings = this.findOrFilter($scope, 'h' + i); 79 | if ($headings.length > 1) { 80 | return i; 81 | } 82 | } 83 | 84 | return 1; 85 | }, 86 | 87 | // returns the elements for the top level, and the next below it 88 | getHeadings: function($scope, topLevel) { 89 | var topSelector = 'h' + topLevel; 90 | 91 | var secondaryLevel = topLevel + 1; 92 | var secondarySelector = 'h' + secondaryLevel; 93 | 94 | return this.findOrFilter($scope, topSelector + ',' + secondarySelector); 95 | }, 96 | 97 | getNavLevel: function(el) { 98 | return parseInt(el.tagName.charAt(1), 10); 99 | }, 100 | 101 | populateNav: function($topContext, topLevel, $headings) { 102 | var $context = $topContext; 103 | var $prevNav; 104 | 105 | var helpers = this; 106 | $headings.each(function(i, el) { 107 | var $newNav = helpers.generateNavItem(el); 108 | var navLevel = helpers.getNavLevel(el); 109 | 110 | // determine the proper $context 111 | if (navLevel === topLevel) { 112 | // use top level 113 | $context = $topContext; 114 | } else if ($prevNav && $context === $topContext) { 115 | // create a new level of the tree and switch to it 116 | $context = helpers.createChildNavList($prevNav); 117 | } // else use the current $context 118 | 119 | $context.append($newNav); 120 | 121 | $prevNav = $newNav; 122 | }); 123 | }, 124 | 125 | parseOps: function(arg) { 126 | var opts; 127 | if (arg.jquery) { 128 | opts = { 129 | $nav: arg 130 | }; 131 | } else { 132 | opts = arg; 133 | } 134 | opts.$scope = opts.$scope || $(document.body); 135 | return opts; 136 | } 137 | }, 138 | 139 | // accepts a jQuery object, or an options object 140 | init: function(opts) { 141 | opts = this.helpers.parseOps(opts); 142 | 143 | // ensure that the data attribute is in place for styling 144 | opts.$nav.attr('data-toggle', 'toc'); 145 | 146 | var $topContext = this.helpers.createChildNavList(opts.$nav); 147 | var topLevel = this.helpers.getTopLevel(opts.$scope); 148 | var $headings = this.helpers.getHeadings(opts.$scope, topLevel); 149 | this.helpers.populateNav($topContext, topLevel, $headings); 150 | } 151 | }; 152 | 153 | $(function() { 154 | $('nav[data-toggle="toc"]').each(function(i, el) { 155 | var $nav = $(el); 156 | Toc.init($nav); 157 | }); 158 | }); 159 | })(); 160 | -------------------------------------------------------------------------------- /docs/docsearch.css: -------------------------------------------------------------------------------- 1 | /* Docsearch -------------------------------------------------------------- */ 2 | /* 3 | Source: https://github.com/algolia/docsearch/ 4 | License: MIT 5 | */ 6 | 7 | .algolia-autocomplete { 8 | display: block; 9 | -webkit-box-flex: 1; 10 | -ms-flex: 1; 11 | flex: 1 12 | } 13 | 14 | .algolia-autocomplete .ds-dropdown-menu { 15 | width: 100%; 16 | min-width: none; 17 | max-width: none; 18 | padding: .75rem 0; 19 | background-color: #fff; 20 | background-clip: padding-box; 21 | border: 1px solid rgba(0, 0, 0, .1); 22 | box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .175); 23 | } 24 | 25 | @media (min-width:768px) { 26 | .algolia-autocomplete .ds-dropdown-menu { 27 | width: 175% 28 | } 29 | } 30 | 31 | .algolia-autocomplete .ds-dropdown-menu::before { 32 | display: none 33 | } 34 | 35 | .algolia-autocomplete .ds-dropdown-menu [class^=ds-dataset-] { 36 | padding: 0; 37 | background-color: rgb(255,255,255); 38 | border: 0; 39 | max-height: 80vh; 40 | } 41 | 42 | .algolia-autocomplete .ds-dropdown-menu .ds-suggestions { 43 | margin-top: 0 44 | } 45 | 46 | .algolia-autocomplete .algolia-docsearch-suggestion { 47 | padding: 0; 48 | overflow: visible 49 | } 50 | 51 | .algolia-autocomplete .algolia-docsearch-suggestion--category-header { 52 | padding: .125rem 1rem; 53 | margin-top: 0; 54 | font-size: 1.3em; 55 | font-weight: 500; 56 | color: #00008B; 57 | border-bottom: 0 58 | } 59 | 60 | .algolia-autocomplete .algolia-docsearch-suggestion--wrapper { 61 | float: none; 62 | padding-top: 0 63 | } 64 | 65 | .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { 66 | float: none; 67 | width: auto; 68 | padding: 0; 69 | text-align: left 70 | } 71 | 72 | .algolia-autocomplete .algolia-docsearch-suggestion--content { 73 | float: none; 74 | width: auto; 75 | padding: 0 76 | } 77 | 78 | .algolia-autocomplete .algolia-docsearch-suggestion--content::before { 79 | display: none 80 | } 81 | 82 | .algolia-autocomplete .ds-suggestion:not(:first-child) .algolia-docsearch-suggestion--category-header { 83 | padding-top: .75rem; 84 | margin-top: .75rem; 85 | border-top: 1px solid rgba(0, 0, 0, .1) 86 | } 87 | 88 | .algolia-autocomplete .ds-suggestion .algolia-docsearch-suggestion--subcategory-column { 89 | display: block; 90 | padding: .1rem 1rem; 91 | margin-bottom: 0.1; 92 | font-size: 1.0em; 93 | font-weight: 400 94 | /* display: none */ 95 | } 96 | 97 | .algolia-autocomplete .algolia-docsearch-suggestion--title { 98 | display: block; 99 | padding: .25rem 1rem; 100 | margin-bottom: 0; 101 | font-size: 0.9em; 102 | font-weight: 400 103 | } 104 | 105 | .algolia-autocomplete .algolia-docsearch-suggestion--text { 106 | padding: 0 1rem .5rem; 107 | margin-top: -.25rem; 108 | font-size: 0.8em; 109 | font-weight: 400; 110 | line-height: 1.25 111 | } 112 | 113 | .algolia-autocomplete .algolia-docsearch-footer { 114 | width: 110px; 115 | height: 20px; 116 | z-index: 3; 117 | margin-top: 10.66667px; 118 | float: right; 119 | font-size: 0; 120 | line-height: 0; 121 | } 122 | 123 | .algolia-autocomplete .algolia-docsearch-footer--logo { 124 | background-image: url("data:image/svg+xml;utf8,"); 125 | background-repeat: no-repeat; 126 | background-position: 50%; 127 | background-size: 100%; 128 | overflow: hidden; 129 | text-indent: -9000px; 130 | width: 100%; 131 | height: 100%; 132 | display: block; 133 | transform: translate(-8px); 134 | } 135 | 136 | .algolia-autocomplete .algolia-docsearch-suggestion--highlight { 137 | color: #FF8C00; 138 | background: rgba(232, 189, 54, 0.1) 139 | } 140 | 141 | 142 | .algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight { 143 | box-shadow: inset 0 -2px 0 0 rgba(105, 105, 105, .5) 144 | } 145 | 146 | .algolia-autocomplete .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content { 147 | background-color: rgba(192, 192, 192, .15) 148 | } 149 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Catch Cheaters • cheatR 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 |
    23 |
    78 | 79 | 80 | 81 | 82 |
    83 |
    84 |
    85 | 88 |

    This is a mini package to help you find cheaters by comparing hand-ins!
    89 | (Read more about the circumstances that brought about the development of this package.)

    90 |
    91 |

    92 | Download and Install

    93 |

    CRAN downloads

    94 |

    You can install cheatR with:

    95 | 96 |

    Or get the dev version from github:

    97 |
    # install.packages("remotes")
     98 | remotes::install_github("mattansb/cheatR")
    99 |

    See the using cheatR vignette, or use the accompanying Shiny app that can either be run locally

    100 |
    cheatR::catch_em_app()
    101 |

    Or can be found on shinyapps.io!

    102 |

    103 |
    104 |
    105 |

    106 | Limitations?

    107 |
      108 |
    • As far as we can tell, this should work on any language; we tried both English and Hebrew, with and without setting Sys.setlocale("LC_ALL", "Hebrew").
      109 |
    • 110 |
    • Best performance was achieved on R version > 3.5.0.
    • 111 |
    112 |
    113 |
    114 |

    115 | Authors

    116 |
      117 |
    • 118 | Mattan S. Ben-Shachar
      aut, cre
      .
    • 119 |
    • 120 | Almog Simchon
      aut, cre
      .
    • 121 |
    122 |
    123 |
    124 |
    125 | 126 | 151 |
    152 | 153 | 154 |
    157 | 158 |
    159 |

    Site built with pkgdown 1.5.1.

    160 |
    161 | 162 |
    163 |
    164 | 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /docs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /docs/news/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Changelog • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
    62 |
    63 | 117 | 118 | 119 | 120 |
    121 | 122 |
    123 |
    124 | 128 | 129 |
    130 | 131 | 136 | 137 |
    138 | 139 | 140 |
    141 | 144 | 145 |
    146 |

    Site built with pkgdown 1.5.1.

    147 |
    148 | 149 |
    150 |
    151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /docs/pkgdown.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer */ 2 | 3 | /** 4 | * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ 5 | * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css 6 | * 7 | * .Site -> body > .container 8 | * .Site-content -> body > .container .row 9 | * .footer -> footer 10 | * 11 | * Key idea seems to be to ensure that .container and __all its parents__ 12 | * have height set to 100% 13 | * 14 | */ 15 | 16 | html, body { 17 | height: 100%; 18 | } 19 | 20 | body { 21 | position: relative; 22 | } 23 | 24 | body > .container { 25 | display: flex; 26 | height: 100%; 27 | flex-direction: column; 28 | } 29 | 30 | body > .container .row { 31 | flex: 1 0 auto; 32 | } 33 | 34 | footer { 35 | margin-top: 45px; 36 | padding: 35px 0 36px; 37 | border-top: 1px solid #e5e5e5; 38 | color: #666; 39 | display: flex; 40 | flex-shrink: 0; 41 | } 42 | footer p { 43 | margin-bottom: 0; 44 | } 45 | footer div { 46 | flex: 1; 47 | } 48 | footer .pkgdown { 49 | text-align: right; 50 | } 51 | footer p { 52 | margin-bottom: 0; 53 | } 54 | 55 | img.icon { 56 | float: right; 57 | } 58 | 59 | img { 60 | max-width: 100%; 61 | } 62 | 63 | /* Fix bug in bootstrap (only seen in firefox) */ 64 | summary { 65 | display: list-item; 66 | } 67 | 68 | /* Typographic tweaking ---------------------------------*/ 69 | 70 | .contents .page-header { 71 | margin-top: calc(-60px + 1em); 72 | } 73 | 74 | dd { 75 | margin-left: 3em; 76 | } 77 | 78 | /* Section anchors ---------------------------------*/ 79 | 80 | a.anchor { 81 | margin-left: -30px; 82 | display:inline-block; 83 | width: 30px; 84 | height: 30px; 85 | visibility: hidden; 86 | 87 | background-image: url(./link.svg); 88 | background-repeat: no-repeat; 89 | background-size: 20px 20px; 90 | background-position: center center; 91 | } 92 | 93 | .hasAnchor:hover a.anchor { 94 | visibility: visible; 95 | } 96 | 97 | @media (max-width: 767px) { 98 | .hasAnchor:hover a.anchor { 99 | visibility: hidden; 100 | } 101 | } 102 | 103 | 104 | /* Fixes for fixed navbar --------------------------*/ 105 | 106 | .contents h1, .contents h2, .contents h3, .contents h4 { 107 | padding-top: 60px; 108 | margin-top: -40px; 109 | } 110 | 111 | /* Navbar submenu --------------------------*/ 112 | 113 | .dropdown-submenu { 114 | position: relative; 115 | } 116 | 117 | .dropdown-submenu>.dropdown-menu { 118 | top: 0; 119 | left: 100%; 120 | margin-top: -6px; 121 | margin-left: -1px; 122 | border-radius: 0 6px 6px 6px; 123 | } 124 | 125 | .dropdown-submenu:hover>.dropdown-menu { 126 | display: block; 127 | } 128 | 129 | .dropdown-submenu>a:after { 130 | display: block; 131 | content: " "; 132 | float: right; 133 | width: 0; 134 | height: 0; 135 | border-color: transparent; 136 | border-style: solid; 137 | border-width: 5px 0 5px 5px; 138 | border-left-color: #cccccc; 139 | margin-top: 5px; 140 | margin-right: -10px; 141 | } 142 | 143 | .dropdown-submenu:hover>a:after { 144 | border-left-color: #ffffff; 145 | } 146 | 147 | .dropdown-submenu.pull-left { 148 | float: none; 149 | } 150 | 151 | .dropdown-submenu.pull-left>.dropdown-menu { 152 | left: -100%; 153 | margin-left: 10px; 154 | border-radius: 6px 0 6px 6px; 155 | } 156 | 157 | /* Sidebar --------------------------*/ 158 | 159 | #pkgdown-sidebar { 160 | margin-top: 30px; 161 | position: -webkit-sticky; 162 | position: sticky; 163 | top: 70px; 164 | } 165 | 166 | #pkgdown-sidebar h2 { 167 | font-size: 1.5em; 168 | margin-top: 1em; 169 | } 170 | 171 | #pkgdown-sidebar h2:first-child { 172 | margin-top: 0; 173 | } 174 | 175 | #pkgdown-sidebar .list-unstyled li { 176 | margin-bottom: 0.5em; 177 | } 178 | 179 | /* bootstrap-toc tweaks ------------------------------------------------------*/ 180 | 181 | /* All levels of nav */ 182 | 183 | nav[data-toggle='toc'] .nav > li > a { 184 | padding: 4px 20px 4px 6px; 185 | font-size: 1.5rem; 186 | font-weight: 400; 187 | color: inherit; 188 | } 189 | 190 | nav[data-toggle='toc'] .nav > li > a:hover, 191 | nav[data-toggle='toc'] .nav > li > a:focus { 192 | padding-left: 5px; 193 | color: inherit; 194 | border-left: 1px solid #878787; 195 | } 196 | 197 | nav[data-toggle='toc'] .nav > .active > a, 198 | nav[data-toggle='toc'] .nav > .active:hover > a, 199 | nav[data-toggle='toc'] .nav > .active:focus > a { 200 | padding-left: 5px; 201 | font-size: 1.5rem; 202 | font-weight: 400; 203 | color: inherit; 204 | border-left: 2px solid #878787; 205 | } 206 | 207 | /* Nav: second level (shown on .active) */ 208 | 209 | nav[data-toggle='toc'] .nav .nav { 210 | display: none; /* Hide by default, but at >768px, show it */ 211 | padding-bottom: 10px; 212 | } 213 | 214 | nav[data-toggle='toc'] .nav .nav > li > a { 215 | padding-left: 16px; 216 | font-size: 1.35rem; 217 | } 218 | 219 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 220 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 221 | padding-left: 15px; 222 | } 223 | 224 | nav[data-toggle='toc'] .nav .nav > .active > a, 225 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 226 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 227 | padding-left: 15px; 228 | font-weight: 500; 229 | font-size: 1.35rem; 230 | } 231 | 232 | /* orcid ------------------------------------------------------------------- */ 233 | 234 | .orcid { 235 | font-size: 16px; 236 | color: #A6CE39; 237 | /* margins are required by official ORCID trademark and display guidelines */ 238 | margin-left:4px; 239 | margin-right:4px; 240 | vertical-align: middle; 241 | } 242 | 243 | /* Reference index & topics ----------------------------------------------- */ 244 | 245 | .ref-index th {font-weight: normal;} 246 | 247 | .ref-index td {vertical-align: top;} 248 | .ref-index .icon {width: 40px;} 249 | .ref-index .alias {width: 40%;} 250 | .ref-index-icons .alias {width: calc(40% - 40px);} 251 | .ref-index .title {width: 60%;} 252 | 253 | .ref-arguments th {text-align: right; padding-right: 10px;} 254 | .ref-arguments th, .ref-arguments td {vertical-align: top;} 255 | .ref-arguments .name {width: 20%;} 256 | .ref-arguments .desc {width: 80%;} 257 | 258 | /* Nice scrolling for wide elements --------------------------------------- */ 259 | 260 | table { 261 | display: block; 262 | overflow: auto; 263 | } 264 | 265 | /* Syntax highlighting ---------------------------------------------------- */ 266 | 267 | pre { 268 | word-wrap: normal; 269 | word-break: normal; 270 | border: 1px solid #eee; 271 | } 272 | 273 | pre, code { 274 | background-color: #f8f8f8; 275 | color: #333; 276 | } 277 | 278 | pre code { 279 | overflow: auto; 280 | word-wrap: normal; 281 | white-space: pre; 282 | } 283 | 284 | pre .img { 285 | margin: 5px 0; 286 | } 287 | 288 | pre .img img { 289 | background-color: #fff; 290 | display: block; 291 | height: auto; 292 | } 293 | 294 | code a, pre a { 295 | color: #375f84; 296 | } 297 | 298 | a.sourceLine:hover { 299 | text-decoration: none; 300 | } 301 | 302 | .fl {color: #1514b5;} 303 | .fu {color: #000000;} /* function */ 304 | .ch,.st {color: #036a07;} /* string */ 305 | .kw {color: #264D66;} /* keyword */ 306 | .co {color: #888888;} /* comment */ 307 | 308 | .message { color: black; font-weight: bolder;} 309 | .error { color: orange; font-weight: bolder;} 310 | .warning { color: #6A0366; font-weight: bolder;} 311 | 312 | /* Clipboard --------------------------*/ 313 | 314 | .hasCopyButton { 315 | position: relative; 316 | } 317 | 318 | .btn-copy-ex { 319 | position: absolute; 320 | right: 0; 321 | top: 0; 322 | visibility: hidden; 323 | } 324 | 325 | .hasCopyButton:hover button.btn-copy-ex { 326 | visibility: visible; 327 | } 328 | 329 | /* headroom.js ------------------------ */ 330 | 331 | .headroom { 332 | will-change: transform; 333 | transition: transform 200ms linear; 334 | } 335 | .headroom--pinned { 336 | transform: translateY(0%); 337 | } 338 | .headroom--unpinned { 339 | transform: translateY(-100%); 340 | } 341 | 342 | /* mark.js ----------------------------*/ 343 | 344 | mark { 345 | background-color: rgba(255, 255, 51, 0.5); 346 | border-bottom: 2px solid rgba(255, 153, 51, 0.3); 347 | padding: 1px; 348 | } 349 | 350 | /* vertical spacing after htmlwidgets */ 351 | .html-widget { 352 | margin-bottom: 10px; 353 | } 354 | 355 | /* fontawesome ------------------------ */ 356 | 357 | .fab { 358 | font-family: "Font Awesome 5 Brands" !important; 359 | } 360 | 361 | /* don't display links in code chunks when printing */ 362 | /* source: https://stackoverflow.com/a/10781533 */ 363 | @media print { 364 | code a:link:after, code a:visited:after { 365 | content: ""; 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $('.navbar-fixed-top').headroom(); 6 | 7 | $('body').css('padding-top', $('.navbar').height() + 10); 8 | $(window).resize(function(){ 9 | $('body').css('padding-top', $('.navbar').height() + 10); 10 | }); 11 | 12 | $('[data-toggle="tooltip"]').tooltip(); 13 | 14 | var cur_path = paths(location.pathname); 15 | var links = $("#navbar ul li a"); 16 | var max_length = -1; 17 | var pos = -1; 18 | for (var i = 0; i < links.length; i++) { 19 | if (links[i].getAttribute("href") === "#") 20 | continue; 21 | // Ignore external links 22 | if (links[i].host !== location.host) 23 | continue; 24 | 25 | var nav_path = paths(links[i].pathname); 26 | 27 | var length = prefix_length(nav_path, cur_path); 28 | if (length > max_length) { 29 | max_length = length; 30 | pos = i; 31 | } 32 | } 33 | 34 | // Add class to parent
  • , and enclosing
  • if in dropdown 35 | if (pos >= 0) { 36 | var menu_anchor = $(links[pos]); 37 | menu_anchor.parent().addClass("active"); 38 | menu_anchor.closest("li.dropdown").addClass("active"); 39 | } 40 | }); 41 | 42 | function paths(pathname) { 43 | var pieces = pathname.split("/"); 44 | pieces.shift(); // always starts with / 45 | 46 | var end = pieces[pieces.length - 1]; 47 | if (end === "index.html" || end === "") 48 | pieces.pop(); 49 | return(pieces); 50 | } 51 | 52 | // Returns -1 if not found 53 | function prefix_length(needle, haystack) { 54 | if (needle.length > haystack.length) 55 | return(-1); 56 | 57 | // Special case for length-0 haystack, since for loop won't run 58 | if (haystack.length === 0) { 59 | return(needle.length === 0 ? 0 : -1); 60 | } 61 | 62 | for (var i = 0; i < haystack.length; i++) { 63 | if (needle[i] != haystack[i]) 64 | return(i); 65 | } 66 | 67 | return(haystack.length); 68 | } 69 | 70 | /* Clipboard --------------------------*/ 71 | 72 | function changeTooltipMessage(element, msg) { 73 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 74 | element.setAttribute('data-original-title', msg); 75 | $(element).tooltip('show'); 76 | element.setAttribute('data-original-title', tooltipOriginalTitle); 77 | } 78 | 79 | if(ClipboardJS.isSupported()) { 80 | $(document).ready(function() { 81 | var copyButton = ""; 82 | 83 | $(".examples, div.sourceCode").addClass("hasCopyButton"); 84 | 85 | // Insert copy buttons: 86 | $(copyButton).prependTo(".hasCopyButton"); 87 | 88 | // Initialize tooltips: 89 | $('.btn-copy-ex').tooltip({container: 'body'}); 90 | 91 | // Initialize clipboard: 92 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { 93 | text: function(trigger) { 94 | return trigger.parentNode.textContent; 95 | } 96 | }); 97 | 98 | clipboardBtnCopies.on('success', function(e) { 99 | changeTooltipMessage(e.trigger, 'Copied!'); 100 | e.clearSelection(); 101 | }); 102 | 103 | clipboardBtnCopies.on('error', function() { 104 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 105 | }); 106 | }); 107 | } 108 | })(window.jQuery || window.$) 109 | -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.7.2 2 | pkgdown: 1.5.1 3 | pkgdown_sha: ~ 4 | articles: 5 | using-cheatR: using-cheatR.html 6 | last_built: 2020-05-04T14:53Z 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/catch_em.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Match cheaters — catch_em • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 118 | 119 | 120 | 121 |
    122 | 123 |
    124 |
    125 | 130 | 131 |
    132 |

    Match cheaters

    133 |
    134 | 135 |
    catch_em(flist, n_grams = 10, time_lim = 1L, progress_bar = TRUE)
    136 | 137 |

    Arguments

    138 | 139 | 140 | 141 | 142 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 153 | 154 | 155 | 156 | 157 | 158 |
    flist

    a list of documents (.doc/.docx/.pdf). A full/relative 143 | path must be provided.

    n_grams

    see ngram package.

    time_lim

    max time in seconds for each comparison. Defult is 1 second, 152 | had no problem comparing documents with 50K words.

    progress_bar

    Should a progress bar be printed to the console?

    159 | 160 |

    Value

    161 | 162 |

    A correlation matrix of class chtrs with each cell indicating the match (0-1) between two of the documents.

    163 | 164 |

    Examples

    165 |
    if (interactive()) { 166 | files <- choose.files() 167 | catch_em(files) 168 | }
    169 |
    170 | 175 |
    176 | 177 | 178 |
    179 | 182 | 183 |
    184 |

    Site built with pkgdown 1.5.1.

    185 |
    186 | 187 |
    188 |
    189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /docs/reference/catch_em_app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Run <code>catch_em()</code> with <code>shiny</code> — catch_em_app • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 118 | 119 | 120 | 121 |
    122 | 123 |
    124 |
    125 | 130 | 131 |
    132 |

    Run catch_em() interactively.

    133 |
    134 | 135 |
    catch_em_app(...)
    136 | 137 |

    Arguments

    138 | 139 | 140 | 141 | 142 | 143 | 144 |
    ...

    Not used.

    145 | 146 |

    Value

    147 | 148 |

    A shiny app object.

    149 | 150 |

    Examples

    151 |
    152 | if (interactive()) { 153 | catch_em_app() 154 | }
    155 |
    156 | 161 |
    162 | 163 | 164 |
    165 | 168 | 169 |
    170 |

    Site built with pkgdown 1.5.1.

    171 |
    172 | 173 |
    174 |
    175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /docs/reference/compare_txt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Match cheaters — compare_txt • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 118 | 119 | 120 | 121 |
    122 | 123 |
    124 |
    125 | 130 | 131 |
    132 |

    Match cheaters

    133 |
    134 | 135 |
    compare_txt(txt1, txt2, n_grams = 10, across = c("both", "txt1", "txt2"))
    136 | 137 |

    Arguments

    138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 |
    txt1, txt2

    character vectors to compare, each of length 1.

    n_grams

    see ngram package.

    across

    How should the percentage of overlap be computed?

    153 | 154 |

    Value

    155 | 156 |

    The percent (0-1) of overlap between the texts

    157 | 158 |

    Examples

    159 |
    text1 <- "My horse is large and white, and I ride it every day." 160 | text2 <- "My mule is large and brown, and I ride it most days." 161 | compare_txt(text1, text2, n_grams = 3)
    #> [1] 0.3
    162 |
    163 |
    164 | 169 |
    170 | 171 | 172 |
    173 | 176 | 177 |
    178 |

    Site built with pkgdown 1.5.1.

    179 |
    180 | 181 |
    182 |
    183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /docs/reference/figures/cheatRball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/docs/reference/figures/cheatRball.png -------------------------------------------------------------------------------- /docs/reference/figures/cheater_graph-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/docs/reference/figures/cheater_graph-1.png -------------------------------------------------------------------------------- /docs/reference/figures/shiny_app.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/docs/reference/figures/shiny_app.PNG -------------------------------------------------------------------------------- /docs/reference/hist.chtrs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | plot histogram of similarity scores — hist.chtrs • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 113 | 114 | 115 | 116 |
    117 | 118 |
    119 |
    120 | 125 | 126 |
    127 |

    plot histogram of similarity scores

    128 |
    129 | 130 |
    # S3 method for chtrs
    131 | hist(object, ...)
    132 | 133 |

    Arguments

    134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
    object

    output of catch_em().

    ...

    passed to hist

    145 | 146 | 147 |
    148 | 153 |
    154 | 155 | 156 |
    157 | 160 | 161 |
    162 |

    Site built with pkgdown 1.5.1.

    163 |
    164 | 165 |
    166 |
    167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /docs/reference/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Function reference • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
    62 |
    63 | 117 | 118 | 119 | 120 |
    121 | 122 |
    123 |
    124 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 153 | 154 | 155 | 156 | 159 | 160 | 161 | 162 | 165 | 166 | 167 | 168 | 169 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 184 | 185 | 186 | 187 | 190 | 191 | 192 | 193 |
    139 |

    Main Functions

    140 |

    141 |
    151 |

    catch_em()

    152 |

    Match cheaters

    157 |

    catch_em_app()

    158 |

    Run catch_em() with shiny

    163 |

    compare_txt()

    164 |

    Match cheaters

    170 |

    Plot and Summary

    171 |

    172 |
    182 |

    plot(<chtrs>) hist(<chtrs>)

    183 |

    Plot cheatrs / histogram of similarity scores

    188 |

    summary(<chtrs>)

    189 |

    Summarise Cheatrs

    194 |
    195 | 196 | 201 |
    202 | 203 | 204 |
    205 | 208 | 209 |
    210 |

    Site built with pkgdown 1.5.1.

    211 |
    212 | 213 |
    214 |
    215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /docs/reference/plot.chtrs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Plot cheatrs / histogram of similarity scores — plot.chtrs • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 118 | 119 | 120 | 121 |
    122 | 123 |
    124 |
    125 | 130 | 131 |
    132 |

    Requires ggraph and ggplot2 to work.

    133 |
    134 | 135 |
    # S3 method for chtrs
    136 | plot(x, weight_range = c(0.4, 1), remove_lonely = TRUE, digits = 0, ...)
    137 | 
    138 | # S3 method for chtrs
    139 | hist(x, ...)
    140 | 141 |

    Arguments

    142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 |
    x

    output of catch_em().

    weight_range

    range of edge values to plot

    remove_lonely

    should lonely nodes (not connected to any edges) be 155 | removed from the graph?

    digits

    Number of digits to round the percentage to.

    ...

    passed to ggraph::ggraph() or ggplot2::geom_histogram.

    166 | 167 |

    Value

    168 | 169 |

    A ggplot2 plot.

    170 | 171 |

    Examples

    172 |
    if (interactive()) { 173 | files <- choose.files() 174 | res <- catch_em(files) 175 | 176 | plot(res) 177 | hist(res) 178 | }
    179 |
    180 | 185 |
    186 | 187 | 188 |
    189 | 192 | 193 |
    194 |

    Site built with pkgdown 1.5.1.

    195 |
    196 | 197 |
    198 |
    199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /docs/reference/print.chtrs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Print Cheatrs — print.chtrs • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 113 | 114 | 115 | 116 |
    117 | 118 |
    119 |
    120 | 125 | 126 |
    127 |

    Print Cheatrs

    128 |
    129 | 130 |
    print.chtrs(x, ...)
    131 | 132 |

    Arguments

    133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
    x

    output of catch_em().

    ...

    not used

    144 | 145 | 146 |
    147 | 152 |
    153 | 154 | 155 |
    156 | 159 | 160 |
    161 |

    Site built with pkgdown 1.5.1.

    162 |
    163 | 164 |
    165 |
    166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /docs/reference/summary.chtrs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Summarise Cheatrs — summary.chtrs • cheatR 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
    63 |
    64 | 118 | 119 | 120 | 121 |
    122 | 123 |
    124 |
    125 | 130 | 131 |
    132 |

    Summarise Cheatrs

    133 |
    134 | 135 |
    # S3 method for chtrs
    136 | summary(object, bad_files = FALSE, ...)
    137 | 138 |

    Arguments

    139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 149 | 150 | 151 | 152 | 153 | 154 |
    object

    output of catch_em().

    bad_files

    logical. Instead of the result matrix, should return instead 148 | the list of bad files (that did not compare / load)? Defaults to FALSE.

    ...

    Not used.

    155 | 156 |

    Value

    157 | 158 |

    The input chtrs matrix, or a list of bad files (when bad_files = TRUE).

    159 | 160 |

    Examples

    161 |
    162 | if (interactive()) { 163 | files <- choose.files() 164 | res <- catch_em(files) 165 | 166 | summary(res, bad_files = TRUE) 167 | }
    168 |
    169 | 174 |
    175 | 176 | 177 | 187 |
    188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /man/catch_em.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/catch_em.R 3 | \name{catch_em} 4 | \alias{catch_em} 5 | \title{Match cheaters} 6 | \usage{ 7 | catch_em(flist, n_grams = 10, time_lim = 1L, progress_bar = TRUE) 8 | } 9 | \arguments{ 10 | \item{flist}{a list of documents (\code{.doc}/\code{.docx}/\code{.pdf}). A full/relative 11 | path must be provided.} 12 | 13 | \item{n_grams}{see \code{\link{ngram}} package.} 14 | 15 | \item{time_lim}{max time in seconds for each comparison. Defult is 1 second, 16 | had no problem comparing documents with 50K words.} 17 | 18 | \item{progress_bar}{Should a progress bar be printed to the console?} 19 | } 20 | \value{ 21 | A correlation matrix of class \code{chtrs} with each cell indicating the match (0-1) between two of the documents. 22 | } 23 | \description{ 24 | Match cheaters 25 | } 26 | \examples{ 27 | if (interactive()) { 28 | files <- choose.files() 29 | catch_em(files) 30 | } 31 | 32 | } 33 | \author{ 34 | Mattan S. Ben-Shachar 35 | } 36 | -------------------------------------------------------------------------------- /man/catch_em_app.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shiny.R 3 | \name{catch_em_app} 4 | \alias{catch_em_app} 5 | \title{Run \code{catch_em()} with \code{shiny}} 6 | \usage{ 7 | catch_em_app(...) 8 | } 9 | \arguments{ 10 | \item{...}{Not used.} 11 | } 12 | \value{ 13 | A \code{shiny} app object. 14 | } 15 | \description{ 16 | Run \code{catch_em()} interactively. 17 | } 18 | \examples{ 19 | 20 | if (interactive()) { 21 | catch_em_app() 22 | } 23 | 24 | } 25 | \author{ 26 | Almog Simchon 27 | } 28 | -------------------------------------------------------------------------------- /man/compare_txt.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/catch_em.R 3 | \name{compare_txt} 4 | \alias{compare_txt} 5 | \title{Match cheaters} 6 | \usage{ 7 | compare_txt(txt1, txt2, n_grams = 10, across = c("both", "txt1", "txt2")) 8 | } 9 | \arguments{ 10 | \item{txt1, txt2}{character vectors to compare, each of length 1.} 11 | 12 | \item{n_grams}{see \link{ngram} package.} 13 | 14 | \item{across}{How should the percentage of overlap be computed?} 15 | } 16 | \value{ 17 | The percent (0-1) of overlap between the texts 18 | } 19 | \description{ 20 | Match cheaters 21 | } 22 | \examples{ 23 | text1 <- "My horse is large and white, and I ride it every day." 24 | text2 <- "My mule is large and brown, and I ride it most days." 25 | compare_txt(text1, text2, n_grams = 3) 26 | 27 | } 28 | \author{ 29 | Mattan S. Ben-Shachar 30 | } 31 | -------------------------------------------------------------------------------- /man/figures/cheatRball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/man/figures/cheatRball.png -------------------------------------------------------------------------------- /man/figures/cheater_graph-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/man/figures/cheater_graph-1.png -------------------------------------------------------------------------------- /man/figures/shiny_app.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/man/figures/shiny_app.PNG -------------------------------------------------------------------------------- /man/files/paper1_copy1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/man/files/paper1_copy1.docx -------------------------------------------------------------------------------- /man/files/paper1_copy2.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/man/files/paper1_copy2.docx -------------------------------------------------------------------------------- /man/files/paper1_copy3.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/man/files/paper1_copy3.docx -------------------------------------------------------------------------------- /man/files/paper2_copy1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/cheatR/6309dc64cd2c2bb7358b7e8d02cc6e26b78b8eac/man/files/paper2_copy1.docx -------------------------------------------------------------------------------- /man/plot.chtrs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/methods.R 3 | \name{plot.chtrs} 4 | \alias{plot.chtrs} 5 | \alias{hist.chtrs} 6 | \title{Plot cheatrs / histogram of similarity scores} 7 | \usage{ 8 | \method{plot}{chtrs}(x, weight_range = c(0.4, 1), remove_lonely = TRUE, digits = 0, ...) 9 | 10 | \method{hist}{chtrs}(x, ...) 11 | } 12 | \arguments{ 13 | \item{x}{output of \code{\link[=catch_em]{catch_em()}}.} 14 | 15 | \item{weight_range}{range of edge values to plot} 16 | 17 | \item{remove_lonely}{should lonely nodes (not connected to any edges) be 18 | removed from the graph?} 19 | 20 | \item{digits}{Number of digits to round the percentage to.} 21 | 22 | \item{...}{passed to \code{\link[ggraph:ggraph]{ggraph::ggraph()}} or \link[ggplot2:geom_histogram]{ggplot2::geom_histogram}.} 23 | } 24 | \value{ 25 | A \code{ggplot2} plot. 26 | } 27 | \description{ 28 | Requires \code{ggraph} and \code{ggplot2} to work. 29 | } 30 | \examples{ 31 | if (interactive()) { 32 | files <- choose.files() 33 | res <- catch_em(files) 34 | 35 | plot(res) 36 | hist(res) 37 | } 38 | 39 | } 40 | \author{ 41 | Mattan S. Ben-Shachar 42 | } 43 | -------------------------------------------------------------------------------- /man/summary.chtrs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/methods.R 3 | \name{summary.chtrs} 4 | \alias{summary.chtrs} 5 | \title{Summarise Cheatrs} 6 | \usage{ 7 | \method{summary}{chtrs}(object, bad_files = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{object}{output of \code{\link[=catch_em]{catch_em()}}.} 11 | 12 | \item{bad_files}{logical. Instead of the result matrix, should return instead 13 | the list of bad files (that did not compare / load)? Defaults to \code{FALSE}.} 14 | 15 | \item{...}{Not used.} 16 | } 17 | \value{ 18 | The input \code{chtrs} matrix, or a list of bad files (when \code{bad_files = TRUE}). 19 | } 20 | \description{ 21 | Summarise Cheatrs 22 | } 23 | \examples{ 24 | 25 | if (interactive()) { 26 | files <- choose.files() 27 | res <- catch_em(files) 28 | 29 | summary(res, bad_files = TRUE) 30 | } 31 | 32 | } 33 | \author{ 34 | Mattan S. Ben-Shachar 35 | } 36 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(cheatR) 3 | 4 | test_check("cheatR") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-chatch_em.R: -------------------------------------------------------------------------------- 1 | context("chtrs") 2 | 3 | test_that("chtrs", { 4 | skip_on_cran() 5 | skip_if_not(interactive()) 6 | 7 | library(cheatR) 8 | # my_files <- list.files(path = '../../man/files', pattern = '.doc', full.names = TRUE) 9 | my_files <- list.files(path = 'man/files', pattern = '.doc', full.names = TRUE) 10 | # my_files 11 | 12 | set.seed(140) 13 | results <- catch_em(flist = my_files, 14 | n_grams = 10, time_lim = 1) 15 | 16 | expect_is(results, "chtrs") 17 | expect_is(results, "matrix") 18 | expect_true(all(diag(results) == 1)) 19 | expect_true(all(is.na(results[upper.tri(results)]))) 20 | expect_equal(results[lower.tri(results)], 21 | c(0.872, 0.901, 0.002, 0.877, 0.002, 0.001), 22 | tol = .001) 23 | 24 | # plot(results, remove_lonely = F) 25 | # summary(results) 26 | # hist(results) 27 | }) 28 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/using-cheatR.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Using `cheatR`" 3 | author: "Mattan S. Ben-Shachar" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Using `cheatR`} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ```{r, include = FALSE} 13 | knitr::opts_chunk$set( 14 | collapse = TRUE, 15 | comment = "#>" 16 | # fig.path = "/man/figures/" 17 | ) 18 | ``` 19 | 20 | ### Scripting 21 | 22 | Create a list of files: 23 | 24 | ```{r} 25 | my_files <- list.files(path = '../man/files/', pattern = '.doc', full.names = TRUE) 26 | my_files 27 | ``` 28 | The first 3 documents are different drafts of the same paper, so we would expect them to be similar to each other. The last document is a draft of a different paper, so it should be dissimilar to the first 3. **All files are about 45K words long.** 29 | 30 | Now we can use `cheatR` to find duplicates. 31 | 32 | The only function, `catch_em`, takes the following input arguments: 33 | 34 | - `flist` - a list of documents (`.doc`/`.docx`/`.pdf`). A full/relative path must be provided. 35 | - `n_grams` - see [`ngram` package](https://github.com/wrathematics/ngram). 36 | - `time_lim` - max time in seconds for each comparison (we found that some corrupt files run forever and crash R, so a time limit might be needed). 37 | 38 | ```{r} 39 | library(cheatR) 40 | results <- catch_em(flist = my_files, 41 | n_grams = 10, time_lim = 1) # defaults 42 | ``` 43 | 44 | The resulting list contains a matrix with the similarity values between each pair of documents: 45 | 46 | ```{r} 47 | results 48 | ``` 49 | 50 | You can also plot the relational graph if you'd like to get a more clear picture of who copied from who. 51 | 52 | ```{r cheater_graph} 53 | plot(results, weight_range = c(0.7, 1), remove_lonely = FALSE) 54 | ``` --------------------------------------------------------------------------------