Skip to content

Commit b9ad515

Browse files
Merge pull request #10 from katehyerinjeon/hw6
hw6
2 parents 0ede6be + 6f7889a commit b9ad515

14 files changed

Lines changed: 203 additions & 72 deletions

.bumpversion.cfg

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
[bumpversion]
2-
current_version = 0.1.0
2+
current_version = 0.0.1
33
commit = True
44
tag = True
55

6-
[bumpversion:file:pynrm/_version.py]
6+
[bumpversion:file:pynrm/__init__.py]
77
search = __version__ = "{current_version}"
88
replace = __version__ = "{new_version}"
99

1010
[bumpversion:file:pyproject.toml]
11-
search = version="{current_version}"
12-
replace = version="{new_version}"
13-
14-
[bumpversion:file:setup.py]
15-
search = version="{current_version}"
16-
replace = version="{new_version}"
11+
search = version = "{current_version}"
12+
replace = version = "{new_version}"

CONTRIBUTING.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,21 @@
1-
TODO
1+
# Contribution Guidelines
2+
Thanks for your interest in contributing! We want to make contributing to this project as easy and transparent as possible whether it is reporting a bug, discussing the current state of the code, submitting a fix, proposing new features, or more.
3+
4+
## Getting Started
5+
We use [GitHub issues](https://github.com/katehyerinjeon/pynrm/issues) to track public bugs. Report a bug by opening a new issue and make sure to include the followings:
6+
7+
- A quick summary of the issue
8+
- Clear and specific steps to reproduce with sample code if possible
9+
- What you expected would happen
10+
- What actually happens
11+
- Side notes possibly including why you think this might be happening or stuff you tried that didn't work
12+
13+
## Making Changes
14+
Pull requests are the best way to propose changes to the codebase as we use [GitHub workflow](https://github.com/katehyerinjeon/pynrm/actions).
15+
16+
1. Fork the repository and create your branch from `main` (or a topic branch from where you want to base your work)
17+
2. Make commits of logical units (rebase your feature branch before submitting if needed) and write commit messages in [proper format](https://github.blog/2022-06-30-write-better-commits-build-better-projects/)
18+
3. Add tests for any new features or code that should be tested
19+
4. Ensure the test suite passes by running `make tests`
20+
5. Make sure your code lints by running `make lint`
21+
6. Push your changes to the topic branch in your fork of the repository and submit a pull request

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,31 @@ Numerous evaluation-selection systems have been devised to produce populations w
1212
`pynrm` provides a simple yet powerful simulation tool to forecast the stochastic impacts of these systems.
1313
One major bottleneck to running these simulations is that as the number of animals bred increases, the size of the matrix grows exponentially.
1414
`pynrm` efficiently solves for the numerator relationship matrix values by tracing up the pedigree for only the relevant ancestors, thereby minimizing computational overhead.
15+
16+
## Basic Usage
17+
### Installation
18+
`pynrm` is available on PyPI:
19+
20+
```shell
21+
$ pip install pyrnm
22+
```
23+
24+
### Supported Features
25+
- Fine-tuned reproduction simulation with user-defined weights
26+
```python
27+
from pynrm.Pedigree import Pedigree
28+
from pynrm.Simulator import Simulator
29+
30+
pedigree = Pedigree()
31+
simulator = Simulator(pedigree, 10, 100, 0.6, 1)
32+
33+
simulator.reproduce()
34+
```
35+
36+
- Data visualization and analysis of simulation results (COMING SOON)
37+
38+
## Documentation
39+
-- LINK TO OFFICIAL DOC COMING SOON --
40+
41+
## Contribution
42+
Contributions are welcome! There are many ways to contribute to this project. Get started [here](CONTRIBUTING.md).

pynrm/Pedigree.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import numpy as np
2+
import pandas as pd
3+
4+
5+
class Pedigree:
6+
def __init__(self, data=None):
7+
dtype = {"gen": int, "sire": pd.Int64Dtype(), "dam": pd.Int64Dtype(), "ebv": float, "sex": str}
8+
9+
if data is not None:
10+
if isinstance(data, pd.DataFrame):
11+
self.data = pd.DataFrame(data=data, columns=["gen", "sire", "dam", "ebv", "sex"]).astype(dtype=dtype)
12+
else:
13+
raise TypeError("'data' must be of type dataframe")
14+
else:
15+
male = pd.DataFrame(
16+
{
17+
"gen": 0,
18+
"sire": None,
19+
"dam": None,
20+
"ebv": np.random.normal(0, 1, size=500),
21+
"sex": "M",
22+
}
23+
)
24+
female = pd.DataFrame(
25+
{
26+
"gen": 0,
27+
"sire": None,
28+
"dam": None,
29+
"ebv": np.random.normal(0, 1, size=500),
30+
"sex": "F",
31+
}
32+
)
33+
data = male.append(female, ignore_index=True)
34+
self.data = pd.DataFrame(data=data, columns=["gen", "sire", "dam", "ebv", "sex"]).astype(dtype=dtype)

pynrm/Simulator.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
import numpy as np
22
import random
33
from .nrm import get_nrm
4-
from .pedigree import pedigree
4+
from .Pedigree import Pedigree
55

66

77
class Simulator:
8-
def __init__(self, male_k, female_k, h, w):
8+
def __init__(self, pedigree, male_k, female_k, h, w):
9+
if not isinstance(pedigree, Pedigree):
10+
raise TypeError("'pedigree' must be of type Pedigree")
11+
if not isinstance(male_k, int):
12+
raise TypeError("'male_k' must be of type int")
13+
if not isinstance(female_k, int):
14+
raise TypeError("'female_k' must be of type int")
15+
if not isinstance(h, float):
16+
raise TypeError("'h' must be of type float")
17+
if not isinstance(w, float):
18+
raise TypeError("'w' must be of type float")
19+
20+
if male_k < 0:
21+
raise ValueError("'male_k' cannot be negative")
22+
if female_k < 0:
23+
raise ValueError("'female_k' cannot be negative")
24+
if h < 0:
25+
raise ValueError("'h' cannot be negative")
26+
if w < 0:
27+
raise ValueError("'w' cannot be negative")
28+
929
self.pedigree = pedigree
10-
self.gen = 0
1130
self.male_k = male_k
1231
self.female_k = female_k
1332
self.h = h
1433
self.w = w
34+
self.gen = 0
1535

1636
def get_ebv(self, sire, dam):
1737
"""
@@ -37,7 +57,7 @@ def get_ebv(self, sire, dam):
3757
w = round(np.random.normal(0, 1), 3)
3858
f = 0.5 * (get_nrm(self.pedigree, sire, sire) + get_nrm(self.pedigree, dam, dam)) - 1
3959

40-
ebv = 0.5 * (self.pedigree.iloc[sire].ebv + self.pedigree.iloc[dam].ebv)
60+
ebv = 0.5 * (self.pedigree.data.iloc[sire].ebv + self.pedigree.data.iloc[dam].ebv)
4161
+v * h_sqrt * np.sqrt((1 - f) / 2)
4262
+w * np.sqrt(1 - self.h)
4363

@@ -56,7 +76,7 @@ def get_adjusted_ebv(self, top_k, candidate):
5676
:rtype: float
5777
"""
5878

59-
ebv = self.pedigree.iloc[candidate].ebv
79+
ebv = self.pedigree.data.iloc[candidate].ebv
6080

6181
# if none selected as top k, then original ebv is used for scoring
6282
if len(top_k) == 0:
@@ -118,15 +138,15 @@ def reproduce(self):
118138
:rtype: dataframe
119139
"""
120140

121-
all_males = self.pedigree[self.pedigree["sex"] == "M"].index.tolist()
122-
all_females = self.pedigree[self.pedigree["sex"] == "F"].index.tolist()
141+
all_males = self.pedigree.data[self.pedigree.data["sex"] == "M"].index.tolist()
142+
all_females = self.pedigree.data[self.pedigree.data["sex"] == "F"].index.tolist()
123143

124144
top_males = self.get_top_k([], all_males, self.male_k)
125145
top_females = self.get_top_k([], all_females, self.female_k)
126146
random.shuffle(top_females)
127147

128148
self.gen += 1
129-
new_pedigree = pedigree
149+
new_pedigree_data = self.pedigree.data
130150

131151
for i in range(len(top_males)):
132152
sire = top_males[i]
@@ -145,8 +165,8 @@ def reproduce(self):
145165
"ebv": ebv,
146166
"sex": random.choices(["M", "F"])[0],
147167
}
148-
new_pedigree = new_pedigree.append(new_animal, ignore_index=True)
168+
new_pedigree_data = new_pedigree_data.append(new_animal, ignore_index=True)
149169

150-
self.pedigree = new_pedigree
170+
self.pedigree = Pedigree(new_pedigree_data)
151171

152-
return new_pedigree
172+
return new_pedigree_data

pynrm/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
# from ._version import __version__
2-
# from .nrm import get_nrm
1+
__version__ = "0.0.1"

pynrm/__main__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
1-
# from .nrm import get_nrm
2-
31
if __name__ == "__main__":
4-
# TODO
52
pass

pynrm/_version.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

pynrm/nrm.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import pandas as pd
2+
from .Pedigree import Pedigree
3+
4+
15
def get_nrm(pedigree, i, j):
26
"""
37
Calculate nrm value of i-th row and j-th column
@@ -10,36 +14,43 @@ def get_nrm(pedigree, i, j):
1014
:raises ValueError: if i or j is not non-negative
1115
"""
1216

13-
if i < 0:
17+
if not isinstance(pedigree, Pedigree):
18+
raise TypeError("'pedigree' must be of type Pedigree")
19+
if i is not pd.NA and not isinstance(i, int):
20+
raise TypeError("'i' must be of type int")
21+
if j is not pd.NA and not isinstance(j, int):
22+
raise TypeError("'j' must be of type int")
23+
24+
if i is not pd.NA and i < 0:
1425
raise ValueError("'i' cannot be negative")
15-
if j < 0:
26+
if j is not pd.NA and j < 0:
1627
raise ValueError("'j' cannot be negative")
1728

1829
# swap i and j if i is larger than j
19-
if i > j:
30+
if i is not pd.NA and j is not pd.NA and i > j:
2031
tmp = i
2132
i = j
2233
j = tmp
2334

2435
# get sire and dam of j from the pedigree
25-
sire = pedigree.iloc[j].sire
26-
dam = pedigree.iloc[j].dam
36+
sire = pedigree.data.iloc[j].sire
37+
dam = pedigree.data.iloc[j].dam
2738

2839
# diagonal - i and j is the same
29-
if i is not None and i == j:
30-
if sire is not None and dam is not None:
31-
res = 1 + 0.5 * get_nrm(sire, dam)
40+
if i is not pd.NA and i == j:
41+
if sire is not pd.NA and dam is not pd.NA:
42+
res = 1 + 0.5 * get_nrm(pedigree, sire, dam)
3243
else:
3344
res = 1
3445

3546
# off-diagonal - i and j is not the same
3647
else:
37-
if sire is not None and dam is not None:
38-
res = 0.5 * (get_nrm(sire, i) + get_nrm(dam, i))
39-
elif sire is not None:
40-
res = 0.5 * get_nrm(sire, i)
41-
elif dam is not None:
42-
res = 0.5 * get_nrm(dam, i)
48+
if sire is not pd.NA and dam is not pd.NA:
49+
res = 0.5 * (get_nrm(pedigree, sire, i) + get_nrm(pedigree, dam, i))
50+
elif sire is not pd.NA:
51+
res = 0.5 * get_nrm(pedigree, sire, i)
52+
elif dam is not pd.NA:
53+
res = 0.5 * get_nrm(pedigree, dam, i)
4354
else:
4455
res = 0
4556

pynrm/pedigree.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)