From 0a537f992881da26a89858bfc93f36a671c5e5cf Mon Sep 17 00:00:00 2001 From: "Thomas A. Christensen II" <25492070+MillironX@users.noreply.github.com> Date: Sat, 19 Jun 2021 19:16:34 -0500 Subject: [PATCH] Run Julia template engine for CI purposes --- LICENSE | 2 +- Project.toml | 21 ++-- README.md | 83 +-------------- docs/Project.toml | 3 + docs/make.jl | 24 +++++ docs/src/index.md | 14 +++ src/BeefBLUP.jl | 261 +--------------------------------------------- test/runtests.jl | 4 + 8 files changed, 60 insertions(+), 352 deletions(-) create mode 100644 docs/Project.toml create mode 100644 docs/make.jl create mode 100644 docs/src/index.md mode change 100755 => 100644 src/BeefBLUP.jl diff --git a/LICENSE b/LICENSE index 82917ea..67ab4c9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2020, Thomas A. Christensen II +Copyright (c) 2021, Thomas A. Christensen II <25492070+MillironX@users.noreply.github.com> and contributors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Project.toml b/Project.toml index 9c8974f..b864f20 100644 --- a/Project.toml +++ b/Project.toml @@ -1,12 +1,13 @@ name = "BeefBLUP" -uuid = "03993faf-e476-444a-86c9-f31e8122fa24" -authors = ["Thomas A. Christensen II <25492070+MillironX@users.noreply.github.com>"] -version = "0.3.0" +uuid = "4c4dd571-0773-455e-9a95-72727008e3f4" +authors = ["Thomas A. Christensen II <25492070+MillironX@users.noreply.github.com> and contributors"] +version = "0.1.0" -[deps] -ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" -CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" -Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +[compat] +julia = "1.3" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/README.md b/README.md index 38996bd..7162f4e 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,3 @@ -# [:cow:]: beefblup +# BeefBLUP -[![GitHub license](https://img.shields.io/github/license/MillironX/beefblup)](https://github.com/MillironX/beefblup/blob/master/LICENSE.md) -[![Join the chat at https://gitter.im/beefblup/community](https://badges.gitter.im/beefblup/community.svg)](https://gitter.im/beefblup/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Github all releases](https://img.shields.io/github/downloads/MillironX/beefblup/total.svg)](https://GitHub.com/MillironX/beefblup/releases) - -beefblup is a program for ranchers to calculate expected breeding -values (EBVs) for their own beef cattle. It is intended to be usable by anyone -without requiring any prior knowledge of computer programming or linear algebra. -Why? It's part of my effort to -**\#KeepEPDsReal** - -## Installation - -1. [Download and install Julia](https://julialang.org/downloads/platform/) -2. Download the [beefblup ZIP - file](https://github.com/MillironX/beefblup/archive/refs/tags/v0.2.zip) and unzip it someplace memorable -3. In your file explorer, copy the address of the "beefblup" folder -4. Launch Julia -5. Type `cd(" **Also Note:** beefblup was written on, and has only been tested with Julia -> v1.2.0 and higher. While this shouldn't affect most everyday users, it might -> affect you if you are stuck on the current LTS version of Julia (v1.0.5). - -### Development Roadmap - -| Version | Feature | Status | -| ------- | ----------------------------------------------------------------------------------- | ------------------ | -| v0.1 | Julia port of original MATLAB script | :heavy_check_mark: | -| v0.2 | Spreadsheet format redesign | :heavy_check_mark: | -| v0.3 | API rewrite (change to function calls and package format instead of script running) | | -| v0.4 | Add GUI for all options | | -| v0.5 | Automatically calculated Age-Of-Dam, Year, and Season fixed-effects | | -| v0.6 | Repeated measurement BLUP (aka dairyblup) | | -| v0.7 | Multiple trait BLUP | | -| v0.8 | Maternal effects BLUP | | -| v0.9 | Genomic BLUP | | -| v0.10 | beefblup binaries | | -| v1.0 | [Finally, RELEASE!!!](https://youtu.be/1CBjxGdgC1w?t=282) | | - -### Bug Reports - -For every bug report, please include at least the following: - -- Platform (Windows, Mac, Fedora, etc) -- Julia version -- beefblup version -- How you are running Julia (From PowerShell, via the REPL, etc.) -- A beefblup spreadsheet that can be used to recreate the issue -- Description of the problem -- Expected behavior -- A screenshot and/or REPL printout - -## License - -Distributed under the 3-Clause BSD License +[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://MillironX.github.io/BeefBLUP.jl/stable) [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://MillironX.github.io/BeefBLUP.jl/dev) [![Build Status](https://travis-ci.com/MillironX/BeefBLUP.jl.svg?branch=master)](https://travis-ci.com/MillironX/BeefBLUP.jl) [![Coverage](https://codecov.io/gh/MillironX/BeefBLUP.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/MillironX/BeefBLUP.jl) [![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000..21b8b1d --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,3 @@ +[deps] +BeefBLUP = "4c4dd571-0773-455e-9a95-72727008e3f4" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" diff --git a/docs/make.jl b/docs/make.jl new file mode 100644 index 0000000..00da129 --- /dev/null +++ b/docs/make.jl @@ -0,0 +1,24 @@ +using BeefBLUP +using Documenter + +DocMeta.setdocmeta!(BeefBLUP, :DocTestSetup, :(using BeefBLUP); recursive=true) + +makedocs(; + modules=[BeefBLUP], + authors="Thomas A. Christensen II <25492070+MillironX@users.noreply.github.com> and contributors", + repo="https://github.com/MillironX/BeefBLUP.jl/blob/{commit}{path}#{line}", + sitename="BeefBLUP.jl", + format=Documenter.HTML(; + prettyurls=get(ENV, "CI", "false") == "true", + canonical="https://MillironX.github.io/BeefBLUP.jl", + assets=String[], + ), + pages=[ + "Home" => "index.md", + ], +) + +deploydocs(; + repo="github.com/MillironX/BeefBLUP.jl", + devbranch="develop", +) diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 0000000..35a45b5 --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,14 @@ +```@meta +CurrentModule = BeefBLUP +``` + +# BeefBLUP + +Documentation for [BeefBLUP](https://github.com/MillironX/BeefBLUP.jl). + +```@index +``` + +```@autodocs +Modules = [BeefBLUP] +``` diff --git a/src/BeefBLUP.jl b/src/BeefBLUP.jl old mode 100755 new mode 100644 index bcfb815..caf1ae8 --- a/src/BeefBLUP.jl +++ b/src/BeefBLUP.jl @@ -1,264 +1,5 @@ -# beefblup -# Julia package for performing single-variate BLUP to find beef cattle -# breeding values -# (C) 2021 Thomas A. Christensen II -# Licensed under BSD-3-Clause License -# cSpell:includeRegExp #.* -# cSpell:includeRegExp ("""|''')[^\1]*\1 - module BeefBLUP -# Import the required packages -using CSV -using DataFrames -using LinearAlgebra -using Dates -using Gtk - -# Main entry-level function - acts just like the script -function beefblup() - - # Ask for an input spreadsheet - path = open_dialog_native( - "Select a beefblup worksheet", - GtkNullContainer(), - ("*.csv", GtkFileFilter("*.csv", name="beefblup worksheet")) - ) - - # Ask for an output text filename - savepath = save_dialog_native( - "Save your beefblup results", - GtkNullContainer(), - (GtkFileFilter("*.txt", name="Results file"), - "*.txt") - ) - - # Ask for heritability - print("What is the heritability for this trait?> ") - h2 = parse(Float64, readline(stdin)) - - beefblup(path, savepath, h2) - -end - -function beefblup(datafile::String, h2::Float64) - # Assume the data is named the same as the file without the trailing extension - dataname = join(split(datafile, ".")[1:end - 1]) - - # Create a new results name - resultsfile = string(dataname, "_results.txt") - - # Pass this info on to the worker - beefblup(datafile, resultsfile, h2) -end - -# Main worker function, can perform all the work if given all the user input -function beefblup(path::String, savepath::String, h2::Float64) - - # Import data from a suitable spreadsheet - data = DataFrame(CSV.File(path)) - - # Sort the array by date - sort!(data, :birthdate) - - # Define fields to hold id values for animals and their parents - numanimals = length(data.id) - - # Find the index values for animals and their parents - dam = indexin(data.dam, data.id) - sire = indexin(data.sire, data.id) - - # Extract all of the fixed effects - fixedfx = select(data, Not([:id, :birthdate, :sire, :dam]))[:,1:end - 1] - - # Find any columns that need to be deleted - for i in 1:ncol(fixedfx) - if length(unique(fixedfx[:,i])) <= 1 - @warn string("column '", names(fixedfx)[i], "' does not have any unique animals and will be removed from this analysis") - DataFrames.select!(fixedfx, Not(i)) - end - end - - # Determine how many contemporary groups there are - numtraits = ncol(fixedfx) - numgroups = ones(1, numtraits) - for i in 1:numtraits - numgroups[i] = length(unique(fixedfx[:,i])) - end - - # If there are more groups than animals, then the analysis cannot continue - if sum(numgroups) >= numanimals - throw(ErrorException("there are more contemporary groups than animals")) - end +# Write your package code here. - # Define a "normal" animal as one of the last in the groups, provided that - # all traits do not have null values - normal = Array{String}(undef, 1, numtraits) - for i in 1:numtraits - for j in numanimals:-1:1 - if !ismissing(fixedfx[j,i]) - normal[i] = string(fixedfx[j,i]) - break - end - end - end - - # Form the fixed-effect matrix - X = zeros(Int8, numanimals, floor(Int, sum(numgroups)) - length(numgroups) + 1) - X[:,1] = ones(Int8, 1, numanimals) - - # Create an external counter that will increment through both loops - counter = 2 - - # Store the traits in a string array - adjustedtraits = - Array{String}(undef,floor(Int, sum(numgroups)) - length(numgroups)) - # Iterate through each group - for i in 1:length(normal) - # Find the traits that are present in this trait - localdata = string.(fixedfx[:,i]) - traits = unique(localdata) - # Remove the normal version from the analysis - effecttraits = traits[findall(x -> x != normal[i], traits)] - # Iterate inside of the group - for j in 1:(length(effecttraits)) - matchedindex = findall(x -> x == effecttraits[j], localdata) - X[matchedindex, counter] .= 1 - # Add this trait to the string - adjustedtraits[counter - 1] = traits[j] - # Increment the big counter - counter = counter + 1 - end - end - - # Create an empty matrix for the additive relationship matrix - A = zeros(numanimals, numanimals) - - # Create the additive relationship matrix by the FORTRAN method presented by - # Henderson - for i in 1:numanimals - if !isnothing(dam[i]) && !isnothing(sire[i]) - for j in 1:(i - 1) - A[j,i] = 0.5 * (A[j,sire[i]] + A[j,dam[i]]) - A[i,j] = A[j,i] - end - A[i,i] = 1 + 0.5 * A[sire[i], dam[i]] - elseif !isnothing(dam[i]) && isnothing(sire[i]) - for j in 1:(i - 1) - A[j,i] = 0.5 * A[j,dam[i]] - A[i,j] = A[j,i] - end - A[i,i] = 1 - elseif isnothing(dam[i]) && !isnothing(sire[i]) - for j in 1:(i - 1) - A[j,i] = 0.5 * A[j,sire[i]] - A[i,j] = A[j,i] - end - A[i,i] = 1 - else - for j in 1:(i - 1) - A[j,i] = 0 - A[i,j] = 0 - end - A[i,i] = 1 - end - end - - # Extract the observed data - Y = convert(Array{Float64}, data[:,end]) - - # The random effects matrix - Z = Matrix{Int}(I, numanimals, numanimals) - - # Remove items where there is no data - nullobs = findall(isnothing, Y) - Z[nullobs, nullobs] .= 0 - - # Calculate heritability - λ = (1 - h2) / h2 - - # Use the mixed-model equations - MME = [X' * X X' * Z; Z' * X (Z' * Z) + (inv(A) .* λ)] - MMY = [X' * Y; Z' * Y] - solutions = MME \ MMY - - # Find the accuracies - diaginv = diag(inv(MME)) - reliability = ones(Float64, length(diaginv)) - diaginv .* λ - - # Find how many traits we found BLUE for - numgroups = numgroups .- 1 - - # Extract the names of the traits - fixedfxnames = names(fixedfx) - traitname = names(data)[end] - - # Start printing results to output - fileID = open(savepath, "w") - write(fileID, "beefblup Results Report\n") - write(fileID, "Produced using beefblup (") - write(fileID, "https://github.com/millironx/beefblup") - write(fileID, ")\n\n") - write(fileID, "Input:\t") - write(fileID, path) - write(fileID, "\nAnalysis performed:\t") - write(fileID, string(Dates.today())) - write(fileID, "\nTrait examined:\t") - write(fileID, traitname) - write(fileID, "\n\n") - - # Print base population stats - write(fileID, "Base Population:\n") - for i in 1:length(normal) - write(fileID, "\t") - write(fileID, fixedfxnames[i]) - write(fileID, ":\t") - write(fileID, normal[i]) - write(fileID, "\n") - end - write(fileID, "\tMean ") - write(fileID, traitname) - write(fileID, ":\t") - write(fileID, string(solutions[1])) - write(fileID, "\n\n") - - # Contemporary group adjustments - counter = 2 - write(fileID, "Contemporary Group Effects:\n") - for i in 1:length(numgroups) - write(fileID, "\t") - write(fileID, fixedfxnames[i]) - write(fileID, "\tEffect\tReliability\n") - for j in 1:numgroups[i] - write(fileID, "\t") - write(fileID, adjustedtraits[counter - 1]) - write(fileID, "\t") - write(fileID, string(solutions[counter])) - write(fileID, "\t") - write(fileID, string(reliability[counter])) - write(fileID, "\n") - - counter = counter + 1 - end - write(fileID, "\n") - end - write(fileID, "\n") - - # Expected breeding values - write(fileID, "Expected Breeding Values:\n") - write(fileID, "\tID\tEBV\tReliability\n") - for i in 1:numanimals - write(fileID, "\t") - write(fileID, string(data.id[i])) - write(fileID, "\t") - write(fileID, string(solutions[i + counter - 1])) - write(fileID, "\t") - write(fileID, string(reliability[i + counter - 1])) - write(fileID, "\n") - end - - write(fileID, "\n - END REPORT -") - close(fileID) - -end end diff --git a/test/runtests.jl b/test/runtests.jl index 2fca2e7..9075615 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,2 +1,6 @@ using BeefBLUP using Test + +@testset "BeefBLUP.jl" begin + # Write your tests here. +end