Basic Cluster Tutorial
Tutorial Overview
This tutorial provides a step-by-step demonstration of how to access the cluster and run an R program in parallel. It is designed to supplement the rest of the cluster documentation, not replace it: please review the rest of the documentation before using the cluster.
This tutorial covers:
- Opening a terminal and logging in to the cluster with SSH
- Uploading a file to the cluster
- Running an interactive Slurm session to install packages
- Modifying a basic R program to work with Slurm
- Running the R program in parallel using Slurm job arrays
This tutorial assumes:
- You already have a Cluster Account.
- You are connecting from on-campus (if not, see Connecting to the Cluster).
- You have some basic familiarity with the Linux command line: at minimum
cd
,ls
,mv
,rm
andmkdir
. If not, this tutorial can get you started: https://swcarpentry.github.io/shell-novice/01-intro.html
Tutorial Formatting:
- Examples use the username
yournetid
. Replace this with your NetID. - Lines that include user input are highlighted
- Examples that include many lines of text output are truncated with
...
Connecting to the Cluster
Open a Terminal app and SSH to the cluster. Enter yes
at the key warning. This warning will only appear during your first connection.
me@mylaptop:~> ssh yournetid@cluster.stat.washington.edu
The authenticity of host 'cluster.stat.washington.edu (172.25.49.104)' can't be established.
ED25519 key fingerprint is SHA256:JmeFa2rVARXr0Em2Y6d69ES2waZejAuZkhO+QXDtq5U.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'cluster.stat.washington.edu' (ED25519) to the list of known hosts.
Last login: Fri Aug 23 13:47:39 2024 from 128.95.142.56
yournetid@cls-lg:~$
You are now on the cluster login node. Notice that the prompt has changed to the login node's name cls-lg
. The login node is used for running and monitoring jobs with Slurm commands, and managing files.
You cannot run computing tasks or load software from the login node: those tasks can only be done from a Slurm session.
Uploading Files
The simplest method for transferring files to the cluster is using a graphical tool like Cyberduck or WinSCP. Follow the directions here to install the appropriate app for your computer: Transferring Files Using a Graphical Interface.
- Download these example files:
- In WinSCP or Cyberduck, connect to the cluster by entering the address
sftp://yournetid@cluster.stat.washington.edu
:- In Cyberduck, enter the address in the "Quick Connect" bar.
- In WinSCP, a "New Site" window will appear when WinSCP is first opened. Enter the address in the "Host name" field.
- Drag the previously downloaded example files to your cluster home directory to upload them.
Other File Transfer Methods
In addition to the SFTP based methods listed in Transferring Files with SFTP:
- Code can be transferred with
git clone
- Files can be edited directly on the cluster command line using the nano text editor. Vim and Emacs are also available for advanced users.
Run an Interactive Slurm Session
From the login node, we will use srun
to start an interactive Slurm session. This gives you command line access to a compute node, which is useful for tasks such as:
- Installing libraries
- Testing code before running it in a large batch job
- Running short, single threaded compute tasks. For example, processing the output of a batch job.
We will be using the build partition in this example, which should be used whenever installing libraries or compiling software:
yournetid@cls-lg:~$ srun --pty --time=30 --mem-per-cpu=1000 --partition=build /bin/bash
srun: job 1170717 queued and waiting for resources
srun: job 1170717 has been allocated resources
[build]yournetid@cls-cmp-c1-2:~$
[build]
and the name of the Slurm compute node allocated for this job, cls-cmp-c1-2
.
The srun command above includes parameters setting Slurm resource limits: these will be described later in Run a Parallel Job with Sbatch Arrays.
Loading the R Module
Software on the cluster is loaded using the module
command. This needs to be done at the beginning of any Slurm session:
- List all available software and versions with
module avail
(example output is truncated):[build]yournetid@cls-cmp-c1-2:~$ module avail --------------------------------------------- /sw/opt/lmod/lmod/modulefiles/Core --------------------------------------------- lmod settarg --------------------------------------------------- /sw/ebpkgs/modules/all --------------------------------------------------- ATK/2.38.0-GCCcore-12.2.0 arpack-ng/3.8.0-foss-2022b Abseil/20230125.2-GCCcore-12.2.0 at-spi2-atk/2.38.0-GCCcore-12.2.0 Anaconda3/2023.03-1 at-spi2-core/2.46.0-GCCcore-12.2.0 Armadillo/11.4.3-foss-2022b binutils/2.38-GCCcore-11.3.0 ... R-bundle-CRAN/2024.06-foss-2023b (D) pixman/0.40.0-GCCcore-11.3.0 R/4.2.2-foss-2022b pixman/0.42.2-GCCcore-12.2.0 R/4.3.3-gfbf-2023b pixman/0.42.2-GCCcore-13.2.0 (D) R/4.4.1-gfbf-2023b (D) pkgconf/1.8.0-GCCcore-11.3.0 ...
-
Load the R-bundle-CRAN module:
module load R-bundle-CRAN
. This module loads R along with a large number of commonly used R libraries. Since the version has not been specified (i.e.module load R-bundle-CRAN/2024.06
, the default version labeled with(D)
will be loaded. -
List the loaded modules with
module list
. Notice that this will include a large list of libraries that are loaded as dependencies for R and the R libraries loaded the theR-bundle-CRAN
module:[build]yournetid@cls-cmp-c1-2:~$ module list Currently Loaded Modules: 1) GCCcore/13.2.0 23) foss/2023b 45) libGLU/9.0.3-GCCcore-13.2.0 67) R/4.4.1-gfbf-2023b 89) JasPer/4.0.0-GCCcore-13.2.0 111) Eigen/3.4.0-GCCcore-13.2.0 2) zlib/1.2.13-GCCcore-13.2.0 24) gfbf/2023b 46) pixman/0.42.2-GCCcore-13.2.0 68) GMP/6.3.0-GCCcore-13.2.0 90) LittleCMS/2.15-GCCcore-13.2.0 112) arpack-ng/3.9.0-foss-2023b 3) binutils/2.40-GCCcore-13.2.0 25) bzip2/1.0.8-GCCcore-13.2.0 47) libiconv/1.17-GCCcore-13.2.0 69) NLopt/2.7.1-GCCcore-13.2.0 91) ImageMagick/7.1.1-34-GCCcore-13.2.0 113) Armadillo/12.8.0-foss-2023b 4) GCC/13.2.0 26) expat/2.5.0-GCCcore-13.2.0 48) gettext/0.22-GCCcore-13.2.0 70) libogg/1.3.5-GCCcore-13.2.0 92) GLPK/5.0-GCCcore-13.2.0 114) CFITSIO/4.3.1-GCCcore-13.2.0 5) numactl/2.0.16-GCCcore-13.2.0 27) libpng/1.6.40-GCCcore-13.2.0 49) PCRE2/10.42-GCCcore-13.2.0 71) FLAC/1.4.3-GCCcore-13.2.0 93) nodejs/20.9.0-GCCcore-13.2.0 115) giflib/5.2.1-GCCcore-13.2.0 6) XZ/5.4.4-GCCcore-13.2.0 28) Brotli/1.1.0-GCCcore-13.2.0 50) GLib/2.78.1-GCCcore-13.2.0 72) libvorbis/1.3.7-GCCcore-13.2.0 94) Python/3.11.5-GCCcore-13.2.0 116) json-c/0.17-GCCcore-13.2.0 7) libxml2/2.11.5-GCCcore-13.2.0 29) freetype/2.13.2-GCCcore-13.2.0 51) cairo/1.18.0-GCCcore-13.2.0 73) libopus/1.5.2-GCCcore-13.2.0 95) netCDF/4.9.2-gompi-2023b 117) Xerces-C++/3.2.5-GCCcore-13.2.0 8) libpciaccess/0.17-GCCcore-13.2.0 30) ncurses/6.4-GCCcore-13.2.0 52) libreadline/8.2-GCCcore-13.2.0 74) LAME/3.100-GCCcore-13.2.0 96) GEOS/3.12.1-GCC-13.2.0 118) Imath/3.1.9-GCCcore-13.2.0 9) hwloc/2.9.2-GCCcore-13.2.0 31) util-linux/2.39-GCCcore-13.2.0 53) Tcl/8.6.13-GCCcore-13.2.0 75) libsndfile/1.2.2-GCCcore-13.2.0 97) libarchive/3.7.2-GCCcore-13.2.0 119) OpenEXR/3.2.0-GCCcore-13.2.0 10) OpenSSL/1.1 32) fontconfig/2.14.2-GCCcore-13.2.0 54) SQLite/3.43.1-GCCcore-13.2.0 76) Szip/2.1.1-GCCcore-13.2.0 98) PCRE/8.45-GCCcore-13.2.0 120) Brunsli/0.1-GCCcore-13.2.0 11) libevent/2.1.12-GCCcore-13.2.0 33) xorg-macros/1.20.0-GCCcore-13.2.0 55) NASM/2.16.01-GCCcore-13.2.0 77) HDF5/1.14.3-gompi-2023b 99) nlohmann_json/3.11.3-GCCcore-13.2.0 121) Qhull/2020.2-GCCcore-13.2.0 12) UCX/1.15.0-GCCcore-13.2.0 34) X11/20231019-GCCcore-13.2.0 56) libjpeg-turbo/3.0.1-GCCcore-13.2.0 78) UDUNITS/2.2.28-GCCcore-13.2.0 100) PROJ/9.3.1-GCCcore-13.2.0 122) LERC/4.0.0-GCCcore-13.2.0 13) libfabric/1.19.0-GCCcore-13.2.0 35) gzip/1.13-GCCcore-13.2.0 57) jbigkit/2.1-GCCcore-13.2.0 79) GSL/2.7-GCC-13.2.0 101) libgeotiff/1.7.3-GCCcore-13.2.0 123) OpenJPEG/2.5.0-GCCcore-13.2.0 14) PMIx/4.2.6-GCCcore-13.2.0 36) lz4/1.9.4-GCCcore-13.2.0 58) libdeflate/1.19-GCCcore-13.2.0 80) ATK/2.38.0-GCCcore-13.2.0 102) cffi/1.15.1-GCCcore-13.2.0 124) SWIG/4.1.1-GCCcore-13.2.0 15) UCC/1.2.0-GCCcore-13.2.0 37) zstd/1.5.5-GCCcore-13.2.0 59) LibTIFF/4.6.0-GCCcore-13.2.0 81) DBus/1.15.8-GCCcore-13.2.0 103) cryptography/41.0.5-GCCcore-13.2.0 125) GDAL/3.9.0-foss-2023b 16) OpenMPI/4.1.6-GCC-13.2.0 38) libdrm/2.4.117-GCCcore-13.2.0 60) Java/11.0.18 82) at-spi2-core/2.50.0-GCCcore-13.2.0 104) virtualenv/20.24.6-GCCcore-13.2.0 126) MPFR/4.2.1-GCCcore-13.2.0 17) OpenBLAS/0.3.24-GCC-13.2.0 39) libglvnd/1.7.0-GCCcore-13.2.0 61) libgit2/1.7.2-GCCcore-13.2.0 83) at-spi2-atk/2.38.0-GCCcore-13.2.0 105) Python-bundle-PyPI/2023.10-GCCcore-13.2.0 127) PostgreSQL/16.1-GCCcore-13.2.0 18) FlexiBLAS/3.3.1-GCC-13.2.0 40) libunwind/1.6.2-GCCcore-13.2.0 62) cURL/8.3.0-GCCcore-13.2.0 84) Gdk-Pixbuf/2.42.10-GCCcore-13.2.0 106) pybind11/2.11.1-GCCcore-13.2.0 128) R-bundle-CRAN/2024.06-foss-2023b 19) FFTW/3.3.10-GCC-13.2.0 41) LLVM/16.0.6-GCCcore-13.2.0 63) Tk/8.6.13-GCCcore-13.2.0 85) Pango/1.51.0-GCCcore-13.2.0 107) SciPy-bundle/2023.11-gfbf-2023b 20) gompi/2023b 42) libffi/3.4.4-GCCcore-13.2.0 64) ICU/74.1-GCCcore-13.2.0 86) libepoxy/1.5.10-GCCcore-13.2.0 108) libtirpc/1.3.4-GCCcore-13.2.0 21) FFTW.MPI/3.3.10-gompi-2023b 43) Wayland/1.22.0-GCCcore-13.2.0 65) HarfBuzz/8.2.2-GCCcore-13.2.0 87) GTK3/3.24.39-GCCcore-13.2.0 109) HDF/4.2.16-2-GCCcore-13.2.0 22) ScaLAPACK/2.2.0-gompi-2023b-fb 44) Mesa/23.1.9-GCCcore-13.2.0 66) FriBidi/1.0.13-GCCcore-13.2.0 88) Ghostscript/10.02.1-GCCcore-13.2.0 110) Boost/1.83.0-GCC-13.2.0
Run R & Install A Package
Now that R module has been loaded, we will run R and install a package from CRAN.
[build]yournetid@cls-cmp-c1-2:~$ R
R version 4.3.3 (2024-02-29) -- "Angel Food Cake"
Copyright (C) 2024 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)
...
>
The cluster's R-bundle-CRAN module includes many libraries that are not typically included by default in R. To view the available R libraries, run: installed.packages()
If a library is not available in the cluster R module, it will need to be installed from CRAN. In this example, the palmerpenguins library will be installed. During your first package installation, you will be prompted to:
- Accept the default R library location (
~/Rlibs
): Enteryes
twice - Select a CRAN mirror: Enter
1
> install.packages('palmerpenguins')
Warning in install.packages("palmerpenguins") :
'lib = "/sw/ebpkgs/software/R/4.3.3-gfbf-2023b/lib64/R/library"' is not writable
Would you like to use a personal library instead? (yes/No/cancel) yes
Would you like to create a personal library
‘~/Rlibs/x86_64-pc-linux-gnu/4.3.3’
to install packages into? (yes/No/cancel) yes
--- Please select a CRAN mirror for use in this session ---
Secure CRAN mirrors
1: 0-Cloud [https]
2: Australia (Canberra) [https]
3: Australia (Melbourne 1) [https]
...
75: (other mirrors)
Selection: 1
trying URL 'https://cloud.r-project.org/src/contrib/palmerpenguins_0.1.1.tar.gz'
Content type 'application/x-gzip' length 2995960 bytes (2.9 MB)
==================================================
downloaded 2.9 MB
* installing *source* package ‘palmerpenguins’ ...
** package ‘palmerpenguins’ successfully unpacked and MD5 sums checked
...
* DONE (palmerpenguins)
The downloaded source packages are in
‘/tmp/jobs/yournetid/1193923/Rtmp9jywtX/downloaded_packages’
>
Test loading the library to ensure that it installed successfully, then exit R:
> library('palmerpenguins')
> q()
Save workspace image? [y/n/c]: n
Warning
It is crucial to install packages using only the build
partition. This partition contains the oldest CPU types in the cluster, ensuring that any software installed here will be compatible with all nodes when running batch jobs.
Run an R Script Interactively
Next, we will run one of the R examples uploaded earlier.
Our first example 1-flipper.R
uses the previously installed palmerpenguins library to estimate flipper length by running a simulation 3200 times serially using a for loop. We can test running it in our still active srun interactive session:
[build]yournetid@cls-cmp-c1-2:~/compdocs_example$ Rscript 1-flipper.R
Estimated Average Flipper Length (mm): 201.0051
The interactive session can be closed with exit
or Control + D
to return to the login node:
[build]yournetid@cls-cmp-c1-2:~$ exit
exit
yournetid@cls-lg:~$
Modify R Example for Slurm Parallel Execution
The previous example running Rscript 1-flipper.R
in an srun
session only took a couple of seconds to run, but real workloads on the cluster require parallelization to run efficiently. Let's look at the previous R code example to see how it can be modified to run in parallel using Slurm batch jobs.
library(palmerpenguins)
data <- na.omit(palmerpenguins::penguins)
num_simulations <- 3200
sample_size <- 5
results <- numeric(num_simulations)
# Flipper length simulation:
for (i in 1:num_simulations) {
sampled_data <- data[sample(nrow(data), sample_size, replace = TRUE), ]
results[i] <- mean(sampled_data$flipper_length_mm)
}
# Calculate the overall average from all simulations:
estimated_average_flipper_length <- mean(results)
cat("Estimated Average Flipper Length (mm):", estimated_average_flipper_length, "\n")
To parallelize this, we will rewrite the code to run only 100 iterations serially, rather than 3200. Slurm will be used to run this 32 times in parallel. Since the results will need to be processed in a separate step later, the output will be saved to RDS files. Here is the modified version:
library(palmerpenguins)
data <- na.omit(palmerpenguins::penguins)
# The number of simulations each Slurm job should handle:
num_simulations <- 100
sample_size <- 5
# The iteration number is passed as a command line argument in the sbatch script:
iteration = commandArgs(trailingOnly=TRUE)[1]
results <- numeric(num_simulations)
# Flipper length simulation:
for (i in 1:num_simulations) {
sampled_data <- data[sample(nrow(data), sample_size, replace = TRUE), ]
results[i] <- mean(sampled_data$flipper_length_mm)
}
# Save results to file for later analysis:
dir.create("Rout", showWarnings = FALSE)
output_file <- paste0("Rout/flipper_length_", iteration, ".RDS")
saveRDS(results, file = output_file)
For real workloads, it's generally best to aim for each task to run between 10 minutes and a few hours. If tasks are too short, there may be extra time overhead as Slurm needs to repeatedly schedule new iterations, which can also put unnecessary strain on the scheduler. On the other hand, if tasks run for too long, it can be difficult for Slurm to find available resources to accommodate them.
The optimal task duration will depend on your specific workload. Some workloads allow flexibility in dividing tasks into smaller chunks, while others require longer continuous computation.
Run a Parallel Job with Sbatch Arrays
Now we will use Slurm to run our updated R code 32 times in parallel. This done by creating an sbatch file that defines the Slurm parameters for the job, sets up the computing environment, and executes 2-flipper_slurm.R
:
#!/bin/bash
#SBATCH --job-name=flipper_length
#SBATCH --partition=short
#SBATCH --array=1-32
#SBATCH --ntasks=1
#SBATCH --time=0-00:10
#SBATCH --mem-per-cpu=100
#SBATCH --output=out/%A_%a.out
#SBATCH --error=error/%A_%a.err
module load R-bundle-CRAN
Rscript 2-flipper_slurm.R ${SLURM_ARRAY_TASK_ID}
The lines beginning with #SBATCH
define Slurm parameters for the job:
--partition
: The short partition is best for any jobs that require less than 30GB per task, and less than 12 hours of run time per task.--array=1-32
: This batch job will execute the steps listed at the bottom of the script 32 times in parallel.--ntasks=1
: Each of the 32 iterations run by the job array allocates a single Slurm task.--time=0-0:10
: Each of the 32 tasks is can run up to 10 minutes.--mem-per-cpu=100
: Each of the 32 tasks can consume up to 100MB of memory (RAM).--output=out/%A_%a.out
: Any text output is saved to files inout/
. The file names will include the job ID and array indexes (%A and %a).--error=error/%A_%a.out
: Any error output is saved to files inout/
. This includes any R errors, but does not include Slurm errors.
After the #SBATCH
lines are commands to execute. These commands are in the same format as any terminal command (or bash script):
module load R-bundle-CRAN
: Just like in the interactive srun session, environment modules must be loaded before computational software can be usedRscript 2-flipper_slurm.R ${SLURM_ARRAY_TASK_ID}
: This executes the R script, and passes the environment variable${SLURM_ARRAY_TASK_ID}
as an argument. In2-flipper_slurm.R
, this argument is used as an index to name the saved RDS files.
Finally, we can run the batch job:
yournetid@cls-lg:~$ sbatch run.sbatch
Submitted batch job 1283985
You can check on the status of the running job with squeue --me
. Since this job will finish a few seconds if the cluster is not busy, it is likely that this will return anything:
yournetid@cls-lg:~$ squeue --me
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
1283985_2 short flipper_ yournetid R 0:01 1 cls-cmp-e3-1
1283985_3 short flipper_ yournetid R 0:01 1 cls-cmp-e3-2
1283985_4 short flipper_ yournetid R 0:01 1 cls-cmp-e3-2
1283985_5 short flipper_ yournetid R 0:01 1 cls-cmp-e3-3
1283985_6 short flipper_ yournetid R 0:01 1 cls-cmp-e3-3
...
yournetid@cls-lg:~$
sacct-detail
will show the status of completed or failed jobs. If your job does not run as expected, this is the first troubleshooting step:
yournetid@cls-lg:~$ sacct-detail
JobID JobName Partition State Timelimit Start End Elapsed ReqMem MaxRSS MaxVMSize NNodes NCPUS NodeList ExitCode
1283985_31 flipper_l+ short COMPLETED 00:10:00 2024-10-01T22:16:51 2024-10-01T22:16:57 00:00:06 100M 1 1 cls-cmp-e3-5 0:0
1283985_31.+ batch COMPLETED 2024-10-01T22:16:51 2024-10-01T22:16:57 00:00:06 3168K 80668K 1 1 cls-cmp-e3-5 0:0
1283985_31.+ extern COMPLETED 2024-10-01T22:16:51 2024-10-01T22:16:57 00:00:06 788K 80336K 1 1 cls-cmp-e3-5 0:0
1283985_32 flipper_l+ short COMPLETED 00:10:00 2024-10-01T22:16:51 2024-10-01T22:16:57 00:00:06 100M 1 1 cls-cmp-e3-5 0:0
1283985_32.+ batch COMPLETED 2024-10-01T22:16:51 2024-10-01T22:16:57 00:00:06 3160K 80668K 1 1 cls-cmp-e3-5 0:0
1283985_32.+ extern COMPLETED 2024-10-01T22:16:51 2024-10-01T22:16:57 00:00:06 792K 80336K 1 1 cls-cmp-e3-5 0:0
...
The snippet of sacct-detail
output above includes the following useful details:
JobID
: This is an ID assigned to the job by Slurm, appended with the batch array iteration (in this truncated output example, _31 and _32)State
: If the job failed due to a resource limit, the state might indicate why.Elapsed
: The actual run time of each task in this job.00:00:06
indicates that each step only required 6 seconds to run. This information can be used to fine-tune the--time
parameter for future jobs.MaxRSS
: The actual memory consumption. This is annoyingly outputted in kilobytes, so it needs to be divided by 1024 to compare to the allocation that was set in megabytes (--mem-per-cpu=100
). In the case, we see that the first iteration listed consumed 3.09MB (3168 / 1024), far less than the 100MB limit, so future runs of this code could allocate less memory.
For more details on sacct-detail
and troubleshooting, see Slurm Troubleshooting
Processing Batch Output
After succesfully running our batch job, there are 32 RDS files containing our output:
yournetid@cls-lg:~$ ls Rout
flipper_length_10.RDS flipper_length_17.RDS flipper_length_23.RDS flipper_length_2.RDS flipper_length_6.RDS
flipper_length_11.RDS flipper_length_18.RDS flipper_length_24.RDS flipper_length_30.RDS flipper_length_7.RDS
flipper_length_12.RDS flipper_length_19.RDS flipper_length_25.RDS flipper_length_31.RDS flipper_length_8.RDS
flipper_length_13.RDS flipper_length_1.RDS flipper_length_26.RDS flipper_length_32.RDS flipper_length_9.RDS
flipper_length_14.RDS flipper_length_20.RDS flipper_length_27.RDS flipper_length_3.RDS
flipper_length_15.RDS flipper_length_21.RDS flipper_length_28.RDS flipper_length_4.RDS
flipper_length_16.RDS flipper_length_22.RDS flipper_length_29.RDS flipper_length_5.RDS
Now the results need to be combined with another R script:
# Combine individual estimates from RDS files:
files <- list.files(path = "Rout", pattern = "flipper_length_.*\\.RDS", full.names = TRUE)
all_results <- unlist(lapply(files, readRDS))
# Calculate final estimated average:
final_estimated_average <- mean(all_results)
cat("Combined estimated flipper length (mm):", final_estimated_average, "\n")
srun
session for this step. However, if your workload requires significant processing time, then it would be better to use a sbatch
job instead. Interactive sessions will be killed if your terminal window closes, or if your network connection drops. In contrast, sbatch
jobs run in the background on the cluster, allowing you to safely close your computer and reconnect later to check the results.
yournetid@cls-lg:~$ srun --pty --time=10 --mem-per-cpu=100 --partition=short /bin/bash
srun: job 1291806 queued and waiting for resources
srun: job 1291806 has been allocated resources
[short]yournetid@cls-cmp-e3-1:~$ module load R-bundle-CRAN
[short]yournetid@cls-cmp-e3-1:~$ Rscript 3-combine_results.R
Combined estimated flipper length (mm): 201.018
Help and Feedback
Please contact us at help@stat.washington.edu if you need any assistance, or have any suggestions to improve this tutorial. Any feedback would greatly be appreciated!