tskit 1.0.2__tar.gz → 1.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tskit-1.0.2/tskit.egg-info → tskit-1.0.3}/PKG-INFO +1 -1
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/core.c +1 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_drawing.py +2 -4
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_metadata.py +231 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_phylo_formats.py +92 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/_version.py +1 -1
- {tskit-1.0.2 → tskit-1.0.3}/tskit/drawing.py +18 -7
- {tskit-1.0.2 → tskit-1.0.3}/tskit/metadata.py +148 -1
- {tskit-1.0.2 → tskit-1.0.3}/tskit/tables.py +20 -1
- {tskit-1.0.2 → tskit-1.0.3}/tskit/text_formats.py +13 -1
- {tskit-1.0.2 → tskit-1.0.3}/tskit/trees.py +102 -2
- {tskit-1.0.2 → tskit-1.0.3/tskit.egg-info}/PKG-INFO +1 -1
- {tskit-1.0.2 → tskit-1.0.3}/LICENSE +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/MANIFEST.in +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/README.rst +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/_tskitmodule.c +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/subprojects/kastore/kastore.c +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/subprojects/kastore/kastore.h +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/convert.c +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/convert.h +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/core.h +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/genotypes.c +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/genotypes.h +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/haplotype_matching.c +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/haplotype_matching.h +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/stats.c +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/stats.h +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/tables.c +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/tables.h +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/trees.c +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit/trees.h +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lib/tskit.h +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/lwt_interface/tskit_lwt_interface.h +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/pyproject.toml +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/setup.cfg +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/setup.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_avl_tree.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_balance_metrics.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_cli.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_coalrate.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_combinatorics.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_dict_encoding.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_distance_metrics.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_divmat.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_extend_haplotypes.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_file_format.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_fileobj.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_genotype_matching.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_genotypes.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_haplotype_matching.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_highlevel.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_ibd.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_immutable_table_collection.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_intervals.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_jit.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_ld_matrix.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_ms.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_parsimony.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_provenance.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_python_c.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_reference_sequence.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_relatedness_vector.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_stats.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_table_transforms.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_tables.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_text_formats.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_threads.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_topology.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_tree_positioning.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_tree_stats.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_util.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_utilities.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_vcf.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_version.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tests/test_wright_fisher.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/__init__.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/__main__.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/cli.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/combinatorics.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/exceptions.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/genotypes.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/intervals.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/jit/__init__.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/jit/numba.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/provenance.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/provenance.schema.json +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/stats.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/util.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit/vcf.py +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit.egg-info/SOURCES.txt +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit.egg-info/dependency_links.txt +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit.egg-info/entry_points.txt +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit.egg-info/requires.txt +0 -0
- {tskit-1.0.2 → tskit-1.0.3}/tskit.egg-info/top_level.txt +0 -0
|
@@ -2929,11 +2929,9 @@ class TestDrawKnownSvg(TestDrawSvgBase):
|
|
|
2929
2929
|
# set units
|
|
2930
2930
|
tables.time_units = "generations"
|
|
2931
2931
|
ts = tables.tree_sequence()
|
|
2932
|
-
svg = ts.draw_svg(y_axis=True, debug_box=draw_plotbox)
|
|
2932
|
+
svg = ts.draw_svg(y_axis=True, title="Y axis test", debug_box=draw_plotbox)
|
|
2933
2933
|
assert "Time ago (generations)" in svg
|
|
2934
|
-
self.verify_known_svg(
|
|
2935
|
-
svg, "ts_y_axis.svg", overwrite_viz, width=200 * ts.num_trees
|
|
2936
|
-
)
|
|
2934
|
+
self.verify_known_svg(svg, "ts_y_axis.svg", True, width=200 * ts.num_trees)
|
|
2937
2935
|
|
|
2938
2936
|
def test_known_svg_ts_y_axis_regular(self, overwrite_viz, draw_plotbox):
|
|
2939
2937
|
# This should have gridlines
|
|
@@ -627,6 +627,237 @@ class TestJSONCodec:
|
|
|
627
627
|
assert ms.decode_row(b"") == {}
|
|
628
628
|
|
|
629
629
|
|
|
630
|
+
class TestJSONStructCodec:
|
|
631
|
+
def test_requires_subschemas(self):
|
|
632
|
+
with pytest.raises(
|
|
633
|
+
tskit.MetadataSchemaValidationError,
|
|
634
|
+
match="requires 'json' and 'struct' schema mappings",
|
|
635
|
+
):
|
|
636
|
+
tskit.MetadataSchema({"codec": "json+struct"})
|
|
637
|
+
|
|
638
|
+
def test_disallow_duplicate_keys(self):
|
|
639
|
+
schema = {
|
|
640
|
+
"codec": "json+struct",
|
|
641
|
+
"json": {"type": "object", "properties": {"x": {"type": "number"}}},
|
|
642
|
+
"struct": {
|
|
643
|
+
"type": "object",
|
|
644
|
+
"properties": {"x": {"type": "number", "binaryFormat": "i"}},
|
|
645
|
+
},
|
|
646
|
+
}
|
|
647
|
+
with pytest.raises(
|
|
648
|
+
tskit.MetadataSchemaValidationError, match="must not share property names"
|
|
649
|
+
):
|
|
650
|
+
tskit.MetadataSchema(schema)
|
|
651
|
+
|
|
652
|
+
def test_round_trip_with_struct_and_json(self):
|
|
653
|
+
schema = {
|
|
654
|
+
"codec": "json+struct",
|
|
655
|
+
"json": {
|
|
656
|
+
"type": "object",
|
|
657
|
+
"properties": {
|
|
658
|
+
"label": {"type": "string"},
|
|
659
|
+
"count": {"type": "number"},
|
|
660
|
+
"stuff": {"type": "array"},
|
|
661
|
+
},
|
|
662
|
+
"required": ["label"],
|
|
663
|
+
},
|
|
664
|
+
"struct": {
|
|
665
|
+
"type": "object",
|
|
666
|
+
"properties": {
|
|
667
|
+
"b0": {"type": "number", "binaryFormat": "i"},
|
|
668
|
+
"b1": {"type": "number", "binaryFormat": "i"},
|
|
669
|
+
"xyz": {
|
|
670
|
+
"type": "array",
|
|
671
|
+
"arrayLengthFormat": "H",
|
|
672
|
+
"items": {"type": "number", "binaryFormat": "i"},
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
},
|
|
676
|
+
}
|
|
677
|
+
ms = tskit.MetadataSchema(schema)
|
|
678
|
+
for v in [[], [0, 2, 12], [5] * 1000]:
|
|
679
|
+
row = {
|
|
680
|
+
"label": "abcdef xyz",
|
|
681
|
+
"count": 7,
|
|
682
|
+
"b0": 123,
|
|
683
|
+
"b1": 0,
|
|
684
|
+
"stuff": [1, 3, 2, -1.5, "abc", None],
|
|
685
|
+
"xyz": v,
|
|
686
|
+
"another_thing": "since JSON is permissive this is allowed",
|
|
687
|
+
}
|
|
688
|
+
encoded = ms.validate_and_encode_row(row)
|
|
689
|
+
out = ms.decode_row(encoded)
|
|
690
|
+
assert out == row
|
|
691
|
+
|
|
692
|
+
def schema_with_binary(self, num_binary_ints):
|
|
693
|
+
# produces a json+struct schema having num_binary_ints integers
|
|
694
|
+
# encoded in binary, labeled b0, ... bX
|
|
695
|
+
schema = {
|
|
696
|
+
"codec": "json+struct",
|
|
697
|
+
"json": {
|
|
698
|
+
"type": "object",
|
|
699
|
+
"properties": {
|
|
700
|
+
"label": {"type": "string"},
|
|
701
|
+
"count": {"type": "number"},
|
|
702
|
+
},
|
|
703
|
+
"required": ["label"],
|
|
704
|
+
},
|
|
705
|
+
"struct": {
|
|
706
|
+
"type": "object",
|
|
707
|
+
"properties": {
|
|
708
|
+
f"b{j}": {
|
|
709
|
+
"type": "integer",
|
|
710
|
+
"binaryFormat": "i",
|
|
711
|
+
}
|
|
712
|
+
for j in range(num_binary_ints)
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
}
|
|
716
|
+
return tskit.MetadataSchema(schema)
|
|
717
|
+
|
|
718
|
+
@pytest.mark.parametrize("k", (0, 1, 5, 1001))
|
|
719
|
+
def test_byte_alignment(self, k):
|
|
720
|
+
# We want to test whether the binary portion begins byte-aligned.
|
|
721
|
+
# To verify this, we (somewhat pedantically) let:
|
|
722
|
+
# X = (bytes to encode the json without any binary)
|
|
723
|
+
# Y = (bytes to encode the same json with k ints in binary)
|
|
724
|
+
# and then:
|
|
725
|
+
# (a) if padding is correct, X should be divisible by 8;
|
|
726
|
+
# but to make sure that in fact the binary portion starts after X bytes,
|
|
727
|
+
# we also check that:
|
|
728
|
+
# (b) Y-X is equal to k * (bytes per int)
|
|
729
|
+
ms = self.schema_with_binary(k)
|
|
730
|
+
ms0 = self.schema_with_binary(0)
|
|
731
|
+
bytes_per_int = len(struct.pack("i", 0))
|
|
732
|
+
for s in [
|
|
733
|
+
"",
|
|
734
|
+
"a",
|
|
735
|
+
"ab",
|
|
736
|
+
"abc",
|
|
737
|
+
"abcd",
|
|
738
|
+
"abcde",
|
|
739
|
+
"abcdef",
|
|
740
|
+
"abcdefg",
|
|
741
|
+
"abcdefgh",
|
|
742
|
+
" " * 1000 + "foo" + " " * 1000,
|
|
743
|
+
]:
|
|
744
|
+
row = {"label": s, "count": 7}
|
|
745
|
+
encoded0 = ms0.validate_and_encode_row(row)
|
|
746
|
+
row.update({f"b{j}": j for j in range(k)})
|
|
747
|
+
encoded = ms.validate_and_encode_row(row)
|
|
748
|
+
out = ms.decode_row(encoded)
|
|
749
|
+
assert out == row
|
|
750
|
+
# validate byte alignment
|
|
751
|
+
assert len(encoded0) % 8 == 0
|
|
752
|
+
assert len(encoded) - len(encoded0) == k * bytes_per_int
|
|
753
|
+
|
|
754
|
+
def test_json_defaults_applied(self):
|
|
755
|
+
schema = {
|
|
756
|
+
"codec": "json+struct",
|
|
757
|
+
"json": {
|
|
758
|
+
"type": "object",
|
|
759
|
+
"properties": {"number": {"type": "number", "default": 5}},
|
|
760
|
+
},
|
|
761
|
+
"struct": {"type": "object", "properties": {}},
|
|
762
|
+
}
|
|
763
|
+
ms = tskit.MetadataSchema(schema)
|
|
764
|
+
assert ms.decode_row(ms.validate_and_encode_row({})) == {"number": 5}
|
|
765
|
+
assert ms.decode_row(ms.validate_and_encode_row({"number": 9})) == {"number": 9}
|
|
766
|
+
|
|
767
|
+
def test_nested_default_error(self):
|
|
768
|
+
schema = {
|
|
769
|
+
"codec": "json+struct",
|
|
770
|
+
"json": {
|
|
771
|
+
"type": "object",
|
|
772
|
+
"properties": {
|
|
773
|
+
"obj": {
|
|
774
|
+
"type": "object",
|
|
775
|
+
"properties": {
|
|
776
|
+
"nested_obj_no_default": {
|
|
777
|
+
"type": "object",
|
|
778
|
+
"properties": {},
|
|
779
|
+
},
|
|
780
|
+
"nested_obj": {
|
|
781
|
+
"type": "object",
|
|
782
|
+
"properties": {},
|
|
783
|
+
"default": {"foo": "bar"},
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
}
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
"struct": {"type": "object", "properties": {}},
|
|
790
|
+
}
|
|
791
|
+
with pytest.raises(
|
|
792
|
+
tskit.MetadataSchemaValidationError,
|
|
793
|
+
match="Defaults can only be specified at the top level for JSON codec",
|
|
794
|
+
):
|
|
795
|
+
tskit.MetadataSchema(schema)
|
|
796
|
+
|
|
797
|
+
def test_decode_without_magic_errors(self):
|
|
798
|
+
ms = tskit.MetadataSchema(
|
|
799
|
+
{
|
|
800
|
+
"codec": "json+struct",
|
|
801
|
+
"json": {"type": "object", "properties": {}},
|
|
802
|
+
"struct": {"type": "object", "properties": {}},
|
|
803
|
+
}
|
|
804
|
+
)
|
|
805
|
+
with pytest.raises(ValueError, match="missing magic header"):
|
|
806
|
+
ms.decode_row(b"{}")
|
|
807
|
+
|
|
808
|
+
def test_decode_version_mismatch(self):
|
|
809
|
+
ms = tskit.MetadataSchema(
|
|
810
|
+
{
|
|
811
|
+
"codec": "json+struct",
|
|
812
|
+
"json": {"type": "object", "properties": {}},
|
|
813
|
+
"struct": {"type": "object", "properties": {}},
|
|
814
|
+
}
|
|
815
|
+
)
|
|
816
|
+
header = metadata.JSONStructCodec._HDR.pack(
|
|
817
|
+
metadata.JSONStructCodec.MAGIC,
|
|
818
|
+
metadata.JSONStructCodec.VERSION + 1,
|
|
819
|
+
len(b"{}"),
|
|
820
|
+
0,
|
|
821
|
+
)
|
|
822
|
+
with pytest.raises(
|
|
823
|
+
ValueError,
|
|
824
|
+
match="Unsupported json\\+struct version",
|
|
825
|
+
):
|
|
826
|
+
ms.decode_row(header + b"{}")
|
|
827
|
+
|
|
828
|
+
def test_decode_truncated_lengths(self):
|
|
829
|
+
schema = {
|
|
830
|
+
"codec": "json+struct",
|
|
831
|
+
"json": {"type": "object", "properties": {}},
|
|
832
|
+
"struct": {"type": "object", "properties": {}},
|
|
833
|
+
}
|
|
834
|
+
ms = tskit.MetadataSchema(schema)
|
|
835
|
+
header = metadata.JSONStructCodec._HDR.pack(
|
|
836
|
+
metadata.JSONStructCodec.MAGIC, metadata.JSONStructCodec.VERSION, 5, 0
|
|
837
|
+
)
|
|
838
|
+
with pytest.raises(ValueError, match="declared lengths exceed buffer size"):
|
|
839
|
+
ms.decode_row(header + b"abc")
|
|
840
|
+
|
|
841
|
+
header = metadata.JSONStructCodec._HDR.pack(
|
|
842
|
+
metadata.JSONStructCodec.MAGIC, metadata.JSONStructCodec.VERSION, 1, 3
|
|
843
|
+
)
|
|
844
|
+
with pytest.raises(ValueError, match="declared lengths exceed buffer size"):
|
|
845
|
+
ms.decode_row(header + b"a")
|
|
846
|
+
|
|
847
|
+
def test_missing_struct_property_fails_validation(self):
|
|
848
|
+
schema = {
|
|
849
|
+
"codec": "json+struct",
|
|
850
|
+
"json": {"type": "object", "properties": {}},
|
|
851
|
+
"struct": {
|
|
852
|
+
"type": "object",
|
|
853
|
+
"properties": {"payload": {"type": "integer", "binaryFormat": "i"}},
|
|
854
|
+
},
|
|
855
|
+
}
|
|
856
|
+
ms = tskit.MetadataSchema(schema)
|
|
857
|
+
with pytest.raises(tskit.MetadataValidationError, match="required property"):
|
|
858
|
+
ms.validate_and_encode_row({})
|
|
859
|
+
|
|
860
|
+
|
|
630
861
|
class TestStructCodec:
|
|
631
862
|
def encode_decode(self, method_name, sub_schema, obj, buffer):
|
|
632
863
|
assert (
|
|
@@ -26,6 +26,7 @@ Tests for phylogenetics export functions, newick, nexus, FASTA etc.
|
|
|
26
26
|
|
|
27
27
|
import functools
|
|
28
28
|
import io
|
|
29
|
+
import random
|
|
29
30
|
import textwrap
|
|
30
31
|
|
|
31
32
|
import dendropy
|
|
@@ -334,6 +335,97 @@ class TestNexusIncludeSections:
|
|
|
334
335
|
)
|
|
335
336
|
|
|
336
337
|
|
|
338
|
+
class TestNexusNodeLabels:
|
|
339
|
+
@tests.cached_example
|
|
340
|
+
def balanced_tree(self):
|
|
341
|
+
# 4
|
|
342
|
+
# ┏━┻┓
|
|
343
|
+
# ┃ 3
|
|
344
|
+
# ┃ ┏┻┓
|
|
345
|
+
# 0 1 2
|
|
346
|
+
return tskit.Tree.generate_balanced(3)
|
|
347
|
+
|
|
348
|
+
def test_as_nexus_labels_basic(self):
|
|
349
|
+
ts = self.balanced_tree().tree_sequence
|
|
350
|
+
labels = {0: "human", 1: "chimp", 2: "bonobo"}
|
|
351
|
+
expected = textwrap.dedent(
|
|
352
|
+
"""\
|
|
353
|
+
#NEXUS
|
|
354
|
+
BEGIN TAXA;
|
|
355
|
+
DIMENSIONS NTAX=3;
|
|
356
|
+
TAXLABELS human chimp bonobo;
|
|
357
|
+
END;
|
|
358
|
+
BEGIN TREES;
|
|
359
|
+
TRANSLATE n0 human, n1 chimp, n2 bonobo;
|
|
360
|
+
TREE t0^1 = [&R] (n0:2,(n1:1,n2:1):1);
|
|
361
|
+
END;
|
|
362
|
+
"""
|
|
363
|
+
)
|
|
364
|
+
assert expected == ts.as_nexus(include_alignments=False, node_labels=labels)
|
|
365
|
+
|
|
366
|
+
def test_as_nexus_labels_partial(self):
|
|
367
|
+
ts = self.balanced_tree().tree_sequence
|
|
368
|
+
labels = {0: "human", 2: "bonobo"}
|
|
369
|
+
expected = textwrap.dedent(
|
|
370
|
+
"""\
|
|
371
|
+
#NEXUS
|
|
372
|
+
BEGIN TAXA;
|
|
373
|
+
DIMENSIONS NTAX=3;
|
|
374
|
+
TAXLABELS human n1 bonobo;
|
|
375
|
+
END;
|
|
376
|
+
BEGIN TREES;
|
|
377
|
+
TRANSLATE n0 human, n2 bonobo;
|
|
378
|
+
TREE t0^1 = [&R] (n0:2,(n1:1,n2:1):1);
|
|
379
|
+
END;
|
|
380
|
+
"""
|
|
381
|
+
)
|
|
382
|
+
assert expected == ts.as_nexus(include_alignments=False, node_labels=labels)
|
|
383
|
+
|
|
384
|
+
def test_as_nexus_labels_none(self):
|
|
385
|
+
ts = self.balanced_tree().tree_sequence
|
|
386
|
+
expected = textwrap.dedent(
|
|
387
|
+
"""\
|
|
388
|
+
#NEXUS
|
|
389
|
+
BEGIN TAXA;
|
|
390
|
+
DIMENSIONS NTAX=3;
|
|
391
|
+
TAXLABELS n0 n1 n2;
|
|
392
|
+
END;
|
|
393
|
+
BEGIN TREES;
|
|
394
|
+
TREE t0^1 = [&R] (n0:2,(n1:1,n2:1):1);
|
|
395
|
+
END;
|
|
396
|
+
"""
|
|
397
|
+
)
|
|
398
|
+
assert expected == ts.as_nexus(include_alignments=False, node_labels=None)
|
|
399
|
+
|
|
400
|
+
@pytest.mark.parametrize("ts", get_example_tree_sequences())
|
|
401
|
+
def test_parseable(self, ts):
|
|
402
|
+
def all_samples_are_leaves(ts):
|
|
403
|
+
internal_nodes = np.unique(ts.edges_parent)
|
|
404
|
+
is_internal_sample = np.isin(ts.samples(), internal_nodes)
|
|
405
|
+
return not np.any(is_internal_sample)
|
|
406
|
+
|
|
407
|
+
if not all_samples_are_leaves(ts):
|
|
408
|
+
# TRANSLATE doesn't support translating internal nodes
|
|
409
|
+
return
|
|
410
|
+
|
|
411
|
+
for tree in ts.trees():
|
|
412
|
+
if not tree.has_single_root:
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
labels = {}
|
|
416
|
+
samples = ts.samples()
|
|
417
|
+
k = random.randint(1, len(samples))
|
|
418
|
+
for node in random.sample(list(samples), k):
|
|
419
|
+
labels[node] = f"new_node_which_was_{node}"
|
|
420
|
+
|
|
421
|
+
nexus = ts.as_nexus(include_alignments=False, node_labels=labels)
|
|
422
|
+
ds = dendropy.DataSet.get(data=nexus, schema="nexus")
|
|
423
|
+
tree = ds.tree_lists[0][0]
|
|
424
|
+
dendropy_labels = [node.taxon.label for node in tree.nodes() if node.taxon]
|
|
425
|
+
for label in labels.values():
|
|
426
|
+
assert label.replace("_", " ") in dendropy_labels
|
|
427
|
+
|
|
428
|
+
|
|
337
429
|
class TestNewickCodePaths:
|
|
338
430
|
"""
|
|
339
431
|
Test that the different code paths we use under the hood lead to
|
|
@@ -283,6 +283,15 @@ class Timescaling:
|
|
|
283
283
|
y_scale = self.plot_range / (self.max_time - self.min_time)
|
|
284
284
|
return self.plot_min - (y - self.min_time) * y_scale
|
|
285
285
|
|
|
286
|
+
def with_offset(self, y_offset):
|
|
287
|
+
return Timescaling(
|
|
288
|
+
max_time=self.max_time,
|
|
289
|
+
min_time=self.min_time,
|
|
290
|
+
plot_min=self.plot_min + y_offset,
|
|
291
|
+
plot_range=self.plot_range,
|
|
292
|
+
use_log_transform=self.use_log_transform,
|
|
293
|
+
)
|
|
294
|
+
|
|
286
295
|
|
|
287
296
|
class SVGString(str):
|
|
288
297
|
"A string containing an SVG representation"
|
|
@@ -1460,7 +1469,7 @@ class SvgTreeSequence(SvgAxisPlot):
|
|
|
1460
1469
|
size=subplot_size, num_skipped=tree.index - last_used_index
|
|
1461
1470
|
)
|
|
1462
1471
|
)
|
|
1463
|
-
|
|
1472
|
+
y_offset = self.plotbox.top
|
|
1464
1473
|
if title is not None:
|
|
1465
1474
|
self.add_text_in_group(
|
|
1466
1475
|
title,
|
|
@@ -1481,14 +1490,16 @@ class SvgTreeSequence(SvgAxisPlot):
|
|
|
1481
1490
|
tick_length_upper=self.default_tick_length_site, # TODO - parameterize
|
|
1482
1491
|
x_regions=x_regions,
|
|
1483
1492
|
)
|
|
1484
|
-
y_low = self.tree_plotbox.bottom
|
|
1485
|
-
if y_axis
|
|
1493
|
+
y_low = self.tree_plotbox.bottom + y_offset
|
|
1494
|
+
if self.y_axis:
|
|
1486
1495
|
tscales = {s.timescaling for s in subplots if s.timescaling}
|
|
1487
1496
|
if len(tscales) > 1:
|
|
1488
1497
|
raise ValueError(
|
|
1489
1498
|
"Can't draw a tree sequence Y axis if trees vary in timescale"
|
|
1490
1499
|
)
|
|
1491
|
-
|
|
1500
|
+
# The timescaling of all subplots is used for outer box, but we
|
|
1501
|
+
# need to shift it by the top padding to account for e.g. titles
|
|
1502
|
+
self.timescaling = tscales.pop().with_offset(y_offset)
|
|
1492
1503
|
y_low = self.timescaling.transform(self.timescaling.min_time)
|
|
1493
1504
|
if y_ticks is None:
|
|
1494
1505
|
used_nodes = edge_and_sample_nodes(ts, breaks[skipbreaks])
|
|
@@ -1499,11 +1510,11 @@ class SvgTreeSequence(SvgAxisPlot):
|
|
|
1499
1510
|
|
|
1500
1511
|
self.draw_y_axis(
|
|
1501
1512
|
ticks=check_y_ticks(y_ticks),
|
|
1502
|
-
upper=self.tree_plotbox.top,
|
|
1513
|
+
upper=self.tree_plotbox.top + y_offset,
|
|
1503
1514
|
lower=y_low,
|
|
1504
1515
|
tick_length_outer=self.default_tick_length,
|
|
1505
1516
|
gridlines=y_gridlines,
|
|
1506
|
-
side="right" if y_axis == "right" else "left",
|
|
1517
|
+
side="right" if self.y_axis == "right" else "left",
|
|
1507
1518
|
)
|
|
1508
1519
|
|
|
1509
1520
|
subplot_x = self.plotbox.left
|
|
@@ -1513,7 +1524,7 @@ class SvgTreeSequence(SvgAxisPlot):
|
|
|
1513
1524
|
svg_subplot = container.add(
|
|
1514
1525
|
self.drawing.g(
|
|
1515
1526
|
class_=subplot.svg_class,
|
|
1516
|
-
transform=f"translate({rnd(subplot_x)} {
|
|
1527
|
+
transform=f"translate({rnd(subplot_x)} {y_offset})",
|
|
1517
1528
|
)
|
|
1518
1529
|
)
|
|
1519
1530
|
for svg_items in subplot.root_groups.values():
|
|
@@ -195,6 +195,149 @@ class NOOPCodec(AbstractMetadataCodec):
|
|
|
195
195
|
return data
|
|
196
196
|
|
|
197
197
|
|
|
198
|
+
class JSONStructCodec(AbstractMetadataCodec):
|
|
199
|
+
"""
|
|
200
|
+
Pack canonical JSON metadata together with a struct-encoded binary payload.
|
|
201
|
+
The codec expects a metadata schema with separate ``json`` and ``struct``
|
|
202
|
+
subschemas and produces a single dict containing the union of the keys from
|
|
203
|
+
those subschemas after decoding.
|
|
204
|
+
|
|
205
|
+
The structure of the encoded metadata is as follows: first, a fixed-size
|
|
206
|
+
header that contains the number of bytes for both ``json`` and ``struct``
|
|
207
|
+
portions; next, the json; then a variable number of zeroed padding bytes
|
|
208
|
+
that brings the length to a multiple of 8 for alignment; and finally
|
|
209
|
+
the struct-encoded binary portion.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
MAGIC = b"JBLB"
|
|
213
|
+
VERSION = 1
|
|
214
|
+
_HDR = struct.Struct("<4sBQQ") # magic, version, json_len, blob_len
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def is_schema_trivial(cls, schema: Mapping) -> bool:
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
def __init__(self, schema: Mapping[str, Any]) -> None:
|
|
221
|
+
json_schema = schema.get("json")
|
|
222
|
+
struct_schema = schema.get("struct")
|
|
223
|
+
if not isinstance(json_schema, Mapping) or not isinstance(
|
|
224
|
+
struct_schema, Mapping
|
|
225
|
+
):
|
|
226
|
+
raise exceptions.MetadataSchemaValidationError(
|
|
227
|
+
"json+struct requires 'json' and 'struct' schema mappings"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
json_schema = copy.deepcopy(dict(json_schema, codec="json"))
|
|
231
|
+
struct_schema = copy.deepcopy(dict(struct_schema, codec="struct"))
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
json_schema = JSONCodec.modify_schema(json_schema)
|
|
235
|
+
JSONCodec.schema_validator.check_schema(json_schema)
|
|
236
|
+
except jsonschema.exceptions.SchemaError as ve:
|
|
237
|
+
raise exceptions.MetadataSchemaValidationError(str(ve)) from ve
|
|
238
|
+
try:
|
|
239
|
+
struct_schema = StructCodec.modify_schema(struct_schema)
|
|
240
|
+
StructCodecSchemaValidator.check_schema(struct_schema)
|
|
241
|
+
except jsonschema.exceptions.SchemaError as ve:
|
|
242
|
+
raise exceptions.MetadataSchemaValidationError(str(ve)) from ve
|
|
243
|
+
|
|
244
|
+
self.json_schema = json_schema
|
|
245
|
+
self.struct_schema = struct_schema
|
|
246
|
+
json_props = self.json_schema.get("properties", {})
|
|
247
|
+
struct_props = self.struct_schema.get("properties", {})
|
|
248
|
+
overlap = set(json_props).intersection(struct_props)
|
|
249
|
+
if overlap:
|
|
250
|
+
raise exceptions.MetadataSchemaValidationError(
|
|
251
|
+
"json and struct schemas must not share property names: "
|
|
252
|
+
+ ", ".join(sorted(overlap))
|
|
253
|
+
)
|
|
254
|
+
for name, sub_schema in (
|
|
255
|
+
("json", self.json_schema),
|
|
256
|
+
("struct", self.struct_schema),
|
|
257
|
+
):
|
|
258
|
+
sub_type = sub_schema.get("type")
|
|
259
|
+
if sub_type is None:
|
|
260
|
+
continue
|
|
261
|
+
if isinstance(sub_type, list):
|
|
262
|
+
is_object = "object" in sub_type
|
|
263
|
+
else:
|
|
264
|
+
is_object = sub_type == "object"
|
|
265
|
+
if not is_object:
|
|
266
|
+
raise exceptions.MetadataSchemaValidationError(
|
|
267
|
+
f"{name} subschema must describe an object for json+struct codec"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
self.json_codec = JSONCodec(self.json_schema)
|
|
271
|
+
self.struct_codec = StructCodec(self.struct_schema)
|
|
272
|
+
self._struct_keys = set(struct_props.keys())
|
|
273
|
+
self._validate_json = TSKITMetadataSchemaValidator(self.json_schema).validate
|
|
274
|
+
self._validate_struct = TSKITMetadataSchemaValidator(self.struct_schema).validate
|
|
275
|
+
|
|
276
|
+
def validate_row(self, row: Any) -> None:
|
|
277
|
+
if not isinstance(row, dict):
|
|
278
|
+
raise exceptions.MetadataValidationError(
|
|
279
|
+
"json+struct metadata must be a mapping"
|
|
280
|
+
)
|
|
281
|
+
struct_data = {k: v for k, v in row.items() if k in self._struct_keys}
|
|
282
|
+
json_data = {k: v for k, v in row.items() if k not in self._struct_keys}
|
|
283
|
+
try:
|
|
284
|
+
self._validate_json(json_data)
|
|
285
|
+
self._validate_struct(struct_data)
|
|
286
|
+
except jsonschema.exceptions.ValidationError as ve:
|
|
287
|
+
raise exceptions.MetadataValidationError(str(ve)) from ve
|
|
288
|
+
|
|
289
|
+
def encode(self, obj: Any) -> bytes:
|
|
290
|
+
if not isinstance(obj, dict):
|
|
291
|
+
raise exceptions.MetadataEncodingError(
|
|
292
|
+
"json+struct metadata must be a mapping"
|
|
293
|
+
)
|
|
294
|
+
json_bytes = self.json_codec.encode(
|
|
295
|
+
{k: v for k, v in obj.items() if k not in self._struct_keys}
|
|
296
|
+
)
|
|
297
|
+
blob_bytes = self.struct_codec.encode(
|
|
298
|
+
{k: v for k, v in obj.items() if k in self._struct_keys}
|
|
299
|
+
)
|
|
300
|
+
header = self._HDR.pack(
|
|
301
|
+
self.MAGIC, self.VERSION, len(json_bytes), len(blob_bytes)
|
|
302
|
+
)
|
|
303
|
+
padding_bytes = bytes((8 - ((len(header) + len(json_bytes)) % 8)) % 8)
|
|
304
|
+
return header + json_bytes + padding_bytes + blob_bytes
|
|
305
|
+
|
|
306
|
+
def decode(self, encoded: bytes) -> Any:
|
|
307
|
+
if len(encoded) >= self._HDR.size and encoded[:4] == self.MAGIC:
|
|
308
|
+
_, version, jlen, blen = self._HDR.unpack_from(encoded)
|
|
309
|
+
if version != self.VERSION:
|
|
310
|
+
raise ValueError("Unsupported json+struct version")
|
|
311
|
+
start = self._HDR.size
|
|
312
|
+
# the second mod 8 is here because if start+jlen % 8 is 0 we get 8, not 0
|
|
313
|
+
padding_length = (8 - ((start + jlen) % 8)) % 8
|
|
314
|
+
if (
|
|
315
|
+
jlen > len(encoded) - start
|
|
316
|
+
or blen > len(encoded) - start - jlen - padding_length
|
|
317
|
+
):
|
|
318
|
+
raise ValueError(
|
|
319
|
+
"Invalid json+struct payload: declared lengths exceed buffer size"
|
|
320
|
+
)
|
|
321
|
+
json_bytes = encoded[start : start + jlen]
|
|
322
|
+
blob_bytes = encoded[
|
|
323
|
+
start + jlen + padding_length : start + jlen + padding_length + blen
|
|
324
|
+
]
|
|
325
|
+
json_data = self.json_codec.decode(json_bytes)
|
|
326
|
+
struct_data = self.struct_codec.decode(blob_bytes)
|
|
327
|
+
overlap = set(json_data).intersection(struct_data)
|
|
328
|
+
if overlap:
|
|
329
|
+
raise ValueError(
|
|
330
|
+
"json+struct decoded duplicate keys: " + ", ".join(sorted(overlap))
|
|
331
|
+
)
|
|
332
|
+
combined = dict(json_data)
|
|
333
|
+
combined.update(struct_data)
|
|
334
|
+
return combined
|
|
335
|
+
raise ValueError("Invalid json+struct payload: missing magic header")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
register_metadata_codec(JSONStructCodec, "json+struct")
|
|
339
|
+
|
|
340
|
+
|
|
198
341
|
def binary_format_validator(validator, types, instance, schema):
|
|
199
342
|
# We're hooking into jsonschemas validation code here, which works by creating
|
|
200
343
|
# generators of exceptions, hence the yielding
|
|
@@ -805,7 +948,11 @@ class MetadataSchema:
|
|
|
805
948
|
self._schema = codec_cls.modify_schema(schema)
|
|
806
949
|
self.codec_instance = codec_cls(self._schema)
|
|
807
950
|
self._string = tskit.canonical_json(self._schema)
|
|
808
|
-
self._validate_row =
|
|
951
|
+
self._validate_row = getattr(
|
|
952
|
+
self.codec_instance,
|
|
953
|
+
"validate_row",
|
|
954
|
+
TSKITMetadataSchemaValidator(self._schema).validate,
|
|
955
|
+
)
|
|
809
956
|
self._bypass_validation = codec_cls.is_schema_trivial(schema)
|
|
810
957
|
self.encode_row = self.codec_instance.encode
|
|
811
958
|
self.decode_row = self.codec_instance.decode
|
|
@@ -3455,6 +3455,24 @@ class TableCollection(metadata.MetadataProvider):
|
|
|
3455
3455
|
|
|
3456
3456
|
@classmethod
|
|
3457
3457
|
def load(cls, file_or_path, *, skip_tables=False, skip_reference_sequence=False):
|
|
3458
|
+
"""
|
|
3459
|
+
Load a :class:`TableCollection` from a file or path, saved in the format defined
|
|
3460
|
+
by :meth:`.dump`. Although the file must be in the correct format, unlike
|
|
3461
|
+
:func:`tskit.load` further validation is not performed to check that the tables
|
|
3462
|
+
meet the :ref:`sec_valid_tree_sequence_requirements`. For instance,
|
|
3463
|
+
unsorted tables can be loaded without error and then sorted before
|
|
3464
|
+
:meth:`creating <TableCollection.tree_sequence>` a :class:`TreeSequence`.
|
|
3465
|
+
|
|
3466
|
+
:param file_or_path: The file object or path from which to load the
|
|
3467
|
+
TableCollection.
|
|
3468
|
+
:param bool skip_tables: If True, no tables are read from the file and
|
|
3469
|
+
only the top-level information is populated in the returned TableCollection.
|
|
3470
|
+
:param bool skip_reference_sequence: If True, tables are read
|
|
3471
|
+
without loading any reference sequence.
|
|
3472
|
+
:return: A TableCollection instance
|
|
3473
|
+
:rtype: TableCollection
|
|
3474
|
+
:raises: **tskit.FileFormatError** -- If the file is not in a valid format.
|
|
3475
|
+
"""
|
|
3458
3476
|
file, local_file = util.convert_file_like_to_open_file(file_or_path, "rb")
|
|
3459
3477
|
ll_tc = _tskit.TableCollection()
|
|
3460
3478
|
try:
|
|
@@ -3474,7 +3492,8 @@ class TableCollection(metadata.MetadataProvider):
|
|
|
3474
3492
|
"""
|
|
3475
3493
|
Writes the table collection to the specified path or file object.
|
|
3476
3494
|
|
|
3477
|
-
:param str file_or_path: The file object or path to
|
|
3495
|
+
:param str file_or_path: The file object or path to which to write this
|
|
3496
|
+
TableCollection.
|
|
3478
3497
|
"""
|
|
3479
3498
|
file, local_file = util.convert_file_like_to_open_file(file_or_path, "wb")
|
|
3480
3499
|
try:
|
|
@@ -120,6 +120,7 @@ def write_nexus(
|
|
|
120
120
|
include_alignments,
|
|
121
121
|
reference_sequence,
|
|
122
122
|
missing_data_character,
|
|
123
|
+
node_labels,
|
|
123
124
|
isolated_as_missing=None,
|
|
124
125
|
):
|
|
125
126
|
# See TreeSequence.write_nexus for documentation on parameters.
|
|
@@ -134,7 +135,13 @@ def write_nexus(
|
|
|
134
135
|
print("#NEXUS", file=out)
|
|
135
136
|
print("BEGIN TAXA;", file=out)
|
|
136
137
|
print("", f"DIMENSIONS NTAX={ts.num_samples};", sep=indent, file=out)
|
|
137
|
-
|
|
138
|
+
|
|
139
|
+
if node_labels is not None:
|
|
140
|
+
taxlabels = " ".join(
|
|
141
|
+
node_labels[u] if u in node_labels else f"n{u}" for u in ts.samples()
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
taxlabels = " ".join(f"n{u}" for u in ts.samples())
|
|
138
145
|
print("", f"TAXLABELS {taxlabels};", sep=indent, file=out)
|
|
139
146
|
print("END;", file=out)
|
|
140
147
|
|
|
@@ -166,6 +173,11 @@ def write_nexus(
|
|
|
166
173
|
include_trees = True if include_trees is None else include_trees
|
|
167
174
|
if include_trees:
|
|
168
175
|
print("BEGIN TREES;", file=out)
|
|
176
|
+
|
|
177
|
+
if node_labels is not None:
|
|
178
|
+
translations = ", ".join(f"n{u} {name}" for u, name in node_labels.items())
|
|
179
|
+
print(f" TRANSLATE {translations};", file=out)
|
|
180
|
+
|
|
169
181
|
for tree in ts.trees():
|
|
170
182
|
start_interval = "{0:.{1}f}".format(tree.interval.left, pos_precision)
|
|
171
183
|
end_interval = "{0:.{1}f}".format(tree.interval.right, pos_precision)
|
|
@@ -6797,6 +6797,7 @@ class TreeSequence:
|
|
|
6797
6797
|
reference_sequence=None,
|
|
6798
6798
|
missing_data_character=None,
|
|
6799
6799
|
isolated_as_missing=None,
|
|
6800
|
+
node_labels=None,
|
|
6800
6801
|
):
|
|
6801
6802
|
"""
|
|
6802
6803
|
Returns a `nexus encoding <https://en.wikipedia.org/wiki/Nexus_file>`_
|
|
@@ -6896,6 +6897,10 @@ class TreeSequence:
|
|
|
6896
6897
|
:param str missing_data_character: As for the :meth:`.alignments` method,
|
|
6897
6898
|
but defaults to "?".
|
|
6898
6899
|
:param bool isolated_as_missing: As for the :meth:`.alignments` method.
|
|
6900
|
+
:param node_labels: A map of type `{node_id: name}`. Samples present
|
|
6901
|
+
in the map will have the given name instead of `n{node_id}`. Note
|
|
6902
|
+
that the names must not have whitespace (spaces should be replaced
|
|
6903
|
+
by underscores) or puncuation in them.
|
|
6899
6904
|
:return: A nexus representation of this :class:`TreeSequence`
|
|
6900
6905
|
:rtype: str
|
|
6901
6906
|
"""
|
|
@@ -6908,6 +6913,7 @@ class TreeSequence:
|
|
|
6908
6913
|
reference_sequence=reference_sequence,
|
|
6909
6914
|
missing_data_character=missing_data_character,
|
|
6910
6915
|
isolated_as_missing=isolated_as_missing,
|
|
6916
|
+
node_labels=node_labels,
|
|
6911
6917
|
)
|
|
6912
6918
|
|
|
6913
6919
|
def as_nexus(self, **kwargs):
|
|
@@ -10930,12 +10936,106 @@ class TreeSequence:
|
|
|
10930
10936
|
def ld_matrix(
|
|
10931
10937
|
self,
|
|
10932
10938
|
sample_sets=None,
|
|
10933
|
-
sites=None,
|
|
10934
|
-
positions=None,
|
|
10935
10939
|
mode="site",
|
|
10936
10940
|
stat="r2",
|
|
10941
|
+
sites=None,
|
|
10942
|
+
positions=None,
|
|
10937
10943
|
indexes=None,
|
|
10938
10944
|
):
|
|
10945
|
+
r"""
|
|
10946
|
+
|
|
10947
|
+
Returns a matrix of the specified two-locus statistic (default
|
|
10948
|
+
:math:`r^2`) computed from sample allelic states or branch lengths.
|
|
10949
|
+
The resulting linkage disequilibrium (LD) matrix represents either the
|
|
10950
|
+
two-locus statistic as computed between all pairs of specified
|
|
10951
|
+
``sites`` (``"site"`` mode, producing a
|
|
10952
|
+
``len(sites)``-by-``len(sites)`` sized matrix), or as computed from the
|
|
10953
|
+
branch structures at marginal trees between pairs of trees at all
|
|
10954
|
+
specified ``positions`` (``"branch"`` mode, producing a
|
|
10955
|
+
``len(positions)``-by-``len(positions)`` sized matrix).
|
|
10956
|
+
|
|
10957
|
+
The sites considered for ``"site"`` mode defaults to all sites (which may
|
|
10958
|
+
result in a very large matrix!), but can be restricted using
|
|
10959
|
+
the ``sites`` argument. Sites must be passed as a list of lists,
|
|
10960
|
+
specifying the ``[row_sites, col_sites]``, resulting in a
|
|
10961
|
+
rectangular matrix, or by specifying a single list of ``[sites]``, in
|
|
10962
|
+
which a square matrix will be produced (see
|
|
10963
|
+
:ref:`sec_stats_two_locus_site` for examples). Here, ``sites``,
|
|
10964
|
+
``row_sites``, and ``col_sites`` are each lists of site indexes.
|
|
10965
|
+
|
|
10966
|
+
Similarly, in the ``"branch"`` mode, the ``positions`` argument specifies
|
|
10967
|
+
genomic coordinates at which the expectation for the two-locus statistic
|
|
10968
|
+
is computed, given the local tree structure.
|
|
10969
|
+
(See :ref:`sec_stats_two_locus_branch` for explanation of in what sense
|
|
10970
|
+
this is an expectation.) This defaults to computing
|
|
10971
|
+
the LD for each pair of distinct trees (this is equivalent to passing in
|
|
10972
|
+
the leftmost coordinates of each tree's span, since intervals are closed on
|
|
10973
|
+
the left and open on the right). Similar to the site mode, a nested list
|
|
10974
|
+
of row and column positions can be specified separately (resulting in a
|
|
10975
|
+
rectangular matrix) or a single list of a specified positions results
|
|
10976
|
+
in a square matrix (see :ref:`sec_stats_two_locus_branch` for
|
|
10977
|
+
examples). Like ``sites``, the ``positions`` must be specified as a list
|
|
10978
|
+
of lists.
|
|
10979
|
+
|
|
10980
|
+
Some LD statistics are defined for both within a single set of samples
|
|
10981
|
+
and for two sample sets. If the ``indexes`` argument is specified, then
|
|
10982
|
+
``indexes`` specifies the indexes of the sample sets in the
|
|
10983
|
+
``sample_sets`` list between which to compute LD. For instance, this
|
|
10984
|
+
results in a 3D array whose ``[k,:,:]``-th slice contains LD values
|
|
10985
|
+
between ``sample_sets[i]`` and ``sample_sets[j]``, where ``(i, j)`` is
|
|
10986
|
+
the ``k``-th element of ``indexes``.
|
|
10987
|
+
|
|
10988
|
+
For more on how the ``indexes`` and ``sample_sets`` interact with the
|
|
10989
|
+
output dimensions, see the :ref:`sec_stats_two_locus_sample_sets`
|
|
10990
|
+
section. Statistics are defined in the
|
|
10991
|
+
:ref:`sec_stats_two_locus_summary_functions_two_way` section.
|
|
10992
|
+
|
|
10993
|
+
**Available Stats** (use ``Stat Name`` in the ``stat`` keyword
|
|
10994
|
+
argument). Statistics marked as "multi sample set" allow
|
|
10995
|
+
(but do not require) computation from two sample sets
|
|
10996
|
+
via the ``indexes`` argument.
|
|
10997
|
+
|
|
10998
|
+
======================= ========== ================ ==============
|
|
10999
|
+
Stat Polarised Multi Sample Set Stat Name
|
|
11000
|
+
======================= ========== ================ ==============
|
|
11001
|
+
:math:`r^2` n y "r2"
|
|
11002
|
+
:math:`r` y n "r"
|
|
11003
|
+
:math:`D^2` n y "D2"
|
|
11004
|
+
:math:`D` y n "D"
|
|
11005
|
+
:math:`D'` y n "D_prime"
|
|
11006
|
+
:math:`D_z` n n "Dz"
|
|
11007
|
+
:math:`\pi_2` n n "pi2"
|
|
11008
|
+
:math:`\widehat{D^2}` n y "D2_unbiased"
|
|
11009
|
+
:math:`\widehat{D_z}` n n "Dz_unbiased"
|
|
11010
|
+
:math:`\widehat{\pi_2}` n n "pi2_unbiased"
|
|
11011
|
+
======================= ========== ================ ==============
|
|
11012
|
+
|
|
11013
|
+
:param list sample_sets: A list, or a list of lists of sample node IDs,
|
|
11014
|
+
specifying the groups of nodes to compute the statistic with. Defaults
|
|
11015
|
+
to all samples.
|
|
11016
|
+
:param str mode: A string giving the "type" of the statistic to be
|
|
11017
|
+
computed. Defaults to "site", can be "site" or "branch".
|
|
11018
|
+
:param str stat: A string giving the selected two-locus statistic to
|
|
11019
|
+
compute. Defaults to "r2".
|
|
11020
|
+
:param list sites: A list of lists of sites over which to compute an
|
|
11021
|
+
LD matrix. Can be specified as a list of lists to control the row
|
|
11022
|
+
and column sites. Only available in "site" mode. Specify as
|
|
11023
|
+
``[row_sites, col_sites]`` or ``[all_sites]``.
|
|
11024
|
+
Defaults to all sites.
|
|
11025
|
+
:param list positions: A list of lists of genomic positions where
|
|
11026
|
+
expected LD is computed based on tree topologies and branch
|
|
11027
|
+
lengths. Only applicable in "branch" mode. Specify as a list of
|
|
11028
|
+
two lists to control the row and column positions, as
|
|
11029
|
+
``[row_positions, col_positions]``, or ``[all_positions]``.
|
|
11030
|
+
Defaults to the leftmost coordinates of all trees and computes
|
|
11031
|
+
LD between all pairs of trees.
|
|
11032
|
+
:param list indexes: A list of 2-tuples or a single 2-tuple, specifying
|
|
11033
|
+
the indexes of two sample sets over which to compute a two-way LD
|
|
11034
|
+
statistic. Only :math:`r^2`, :math:`D^2`, and :math:`\widehat{D^2}`
|
|
11035
|
+
are implemented for two-way statistics.
|
|
11036
|
+
:return: A 2D or 3D array of LD matrices.
|
|
11037
|
+
:rtype: numpy.ndarray
|
|
11038
|
+
"""
|
|
10939
11039
|
one_way_stats = {
|
|
10940
11040
|
"D": self._ll_tree_sequence.D_matrix,
|
|
10941
11041
|
"D2": self._ll_tree_sequence.D2_matrix,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|