Document raw source file reference

This commit is contained in:
aseimel
2026-06-15 11:33:18 +02:00
commit b5ca9370f1
61 changed files with 293618 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
_local/
tmp/
outputs/
data/raw/
data/staging/
data/releases/
metadata/release_manifest_*.csv
metadata/scientific_data_release_*.txt
__pycache__/
*/__pycache__/
+663
View File
@@ -0,0 +1,663 @@
# This file is machine-generated - editing it directly is not advised
julia_version = "1.11.3"
manifest_format = "2.0"
project_hash = "5d7dd35b02e3ab85cb2ccc99d83afe86c825420e"
[[deps.AbstractFFTs]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "d92ad398961a3ed262d8bf04a1a2b8340f915fef"
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
version = "1.5.0"
[deps.AbstractFFTs.extensions]
AbstractFFTsChainRulesCoreExt = "ChainRulesCore"
AbstractFFTsTestExt = "Test"
[deps.AbstractFFTs.weakdeps]
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[deps.AliasTables]]
deps = ["PtrArrays", "Random"]
git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff"
uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8"
version = "1.1.3"
[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
version = "1.1.2"
[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
version = "1.11.0"
[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
version = "1.11.0"
[[deps.CSV]]
deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"]
git-tree-sha1 = "8d8e0b0f350b8e1c91420b5e64e5de774c2f0f4d"
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
version = "0.10.16"
[[deps.CategoricalArrays]]
deps = ["Compat", "DataAPI", "Future", "Missings", "Printf", "Requires", "Statistics", "Unicode"]
git-tree-sha1 = "20ff1463035a170b25eba2ef9823bb9ad51635e4"
uuid = "324d7699-5711-5eae-9e2f-1d82baa6b597"
version = "1.1.1"
[deps.CategoricalArrays.extensions]
CategoricalArraysArrowExt = "Arrow"
CategoricalArraysJSONExt = "JSON"
CategoricalArraysRecipesBaseExt = "RecipesBase"
CategoricalArraysSentinelArraysExt = "SentinelArrays"
CategoricalArraysStatsBaseExt = "StatsBase"
CategoricalArraysStructTypesExt = "StructTypes"
[deps.CategoricalArrays.weakdeps]
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
SentinelArrays = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
[[deps.CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.8"
[[deps.Compat]]
deps = ["TOML", "UUIDs"]
git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "4.18.1"
weakdeps = ["Dates", "LinearAlgebra"]
[deps.Compat.extensions]
CompatLinearAlgebraExt = "LinearAlgebra"
[[deps.CompatHelperLocal]]
deps = ["Pkg"]
git-tree-sha1 = "f239f702063ca2fc50844c6cf27b4d497652b474"
uuid = "5224ae11-6099-4aaa-941d-3aab004bd678"
version = "0.1.29"
[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "1.1.1+0"
[[deps.Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.1.1"
[[deps.DataAPI]]
git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.16.0"
[[deps.DataFrames]]
deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "5fab31e2e01e70ad66e3e24c968c264d1cf166d6"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.8.2"
[[deps.DataStructures]]
deps = ["OrderedCollections"]
git-tree-sha1 = "6fb53a69613a0b2b68a0d12671717d307ab8b24e"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.19.5"
[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"
[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
version = "1.11.0"
[[deps.DelimitedFiles]]
deps = ["Mmap"]
git-tree-sha1 = "9e2f36d3c96a820c678f2f1f1782582fcf685bae"
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
version = "1.9.1"
[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
version = "1.11.0"
[[deps.DocStringExtensions]]
git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.9.5"
[[deps.Downloads]]
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
version = "1.6.0"
[[deps.FFTW]]
deps = ["AbstractFFTs", "FFTW_jll", "Libdl", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
git-tree-sha1 = "97f08406df914023af55ade2f843c39e99c5d969"
uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
version = "1.10.0"
[[deps.FFTW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
git-tree-sha1 = "6866aec60ef98e3164cd8d6855225684207e9dff"
uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
version = "3.3.12+0"
[[deps.FilePathsBase]]
deps = ["Compat", "Dates"]
git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2"
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
version = "0.9.24"
weakdeps = ["Mmap", "Test"]
[deps.FilePathsBase.extensions]
FilePathsBaseMmapExt = "Mmap"
FilePathsBaseTestExt = "Test"
[[deps.FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
version = "1.11.0"
[[deps.Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"
version = "1.11.0"
[[deps.HypergeometricFunctions]]
deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"]
git-tree-sha1 = "68c173f4f449de5b438ee67ed0c9c748dc31a2ec"
uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
version = "0.3.28"
[[deps.InlineStrings]]
git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d"
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
version = "1.4.5"
[deps.InlineStrings.extensions]
ArrowTypesExt = "ArrowTypes"
ParsersExt = "Parsers"
[deps.InlineStrings.weakdeps]
ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd"
Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
[[deps.IntelOpenMP_jll]]
deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"]
git-tree-sha1 = "ec1debd61c300961f98064cfb21287613ad7f303"
uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
version = "2025.2.0+0"
[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
version = "1.11.0"
[[deps.InvertedIndices]]
git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.3.1"
[[deps.IrrationalConstants]]
git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.2.6"
[[deps.IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"
[[deps.JLLWrappers]]
deps = ["Artifacts", "Preferences"]
git-tree-sha1 = "7204148362dafe5fe6a273f855b8ccbe4df8173e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.8.0"
[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.4"
[[deps.LaTeXStrings]]
git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.4.0"
[[deps.LazyArtifacts]]
deps = ["Artifacts", "Pkg"]
uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3"
version = "1.11.0"
[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
version = "0.6.4"
[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
version = "8.6.0+0"
[[deps.LibGit2]]
deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
version = "1.11.0"
[[deps.LibGit2_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"]
uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5"
version = "1.7.2+0"
[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
version = "1.11.0+1"
[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
version = "1.11.0"
[[deps.LinearAlgebra]]
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
version = "1.11.0"
[[deps.LogExpFunctions]]
deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "bba2d9aa057d8f126415de240573e86a8f39d2a1"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "1.0.1"
[deps.LogExpFunctions.extensions]
LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables"
LogExpFunctionsInverseFunctionsExt = "InverseFunctions"
[deps.LogExpFunctions.weakdeps]
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112"
[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
version = "1.11.0"
[[deps.MKL_jll]]
deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"]
git-tree-sha1 = "282cadc186e7b2ae0eeadbd7a4dffed4196ae2aa"
uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
version = "2025.2.0+0"
[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
version = "1.11.0"
[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.28.6+0"
[[deps.Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.2.0"
[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
version = "1.11.0"
[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
version = "2023.12.12"
[[deps.NamedTupleTools]]
git-tree-sha1 = "90914795fc59df44120fe3fff6742bb0d7adb1d0"
uuid = "d9ec5142-1e00-5aa0-9d6a-321866360f50"
version = "0.14.3"
[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"
[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
version = "0.3.27+1"
[[deps.OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
version = "0.8.1+2"
[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.6+0"
[[deps.OrderedCollections]]
git-tree-sha1 = "94ba93778373a53bfd5a0caaf7d809c445292ff4"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.8.2"
[[deps.Parameters]]
deps = ["OrderedCollections", "UnPack"]
git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe"
uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a"
version = "0.12.3"
[[deps.Parsers]]
deps = ["Dates", "PrecompileTools", "UUIDs"]
git-tree-sha1 = "468dbe2b510c876dc091b2c74ed52c7c34f48b9b"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.8.5"
[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
version = "1.11.0"
weakdeps = ["REPL"]
[deps.Pkg.extensions]
REPLExt = "REPL"
[[deps.PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.4.3"
[[deps.PrecompileTools]]
deps = ["Preferences"]
git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f"
uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
version = "1.2.1"
[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "8b770b60760d4451834fe79dd483e318eee709c4"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.5.2"
[[deps.PrettyTables]]
deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"]
git-tree-sha1 = "624de6279ab7d94fc9f672f0068107eb6619732c"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "3.3.2"
[deps.PrettyTables.extensions]
PrettyTablesTypstryExt = "Typstry"
[deps.PrettyTables.weakdeps]
Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e"
[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
version = "1.11.0"
[[deps.ProgressMeter]]
deps = ["Distributed", "Printf"]
git-tree-sha1 = "fbb92c6c56b34e1a2c4c36058f68f332bec840e7"
uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
version = "1.11.0"
[[deps.PtrArrays]]
git-tree-sha1 = "4fbbafbc6251b883f4d2705356f3641f3652a7fe"
uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
version = "1.4.0"
[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
version = "1.11.0"
[[deps.Random]]
deps = ["SHA"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
version = "1.11.0"
[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"
[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.1"
[[deps.Rmath]]
deps = ["Random", "Rmath_jll"]
git-tree-sha1 = "5b3d50eb374cea306873b371d3f8d3915a018f0b"
uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
version = "0.9.0"
[[deps.Rmath_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8"
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
version = "0.5.1+0"
[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"
[[deps.SentinelArrays]]
deps = ["Dates", "Random"]
git-tree-sha1 = "084c47c7c5ce5cfecefa0a98dff69eb3646b5a80"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.4.10"
[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
version = "1.11.0"
[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
version = "1.11.0"
[[deps.SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "64d974c2e6fdf07f8155b5b2ca2ffa9069b608d9"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.2.2"
[[deps.SparseArrays]]
deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
version = "1.11.0"
[[deps.SpecialFunctions]]
deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "6547cbdd8ce32efba0d21c5a40fa96d1a3548f9f"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "2.8.0"
[deps.SpecialFunctions.extensions]
SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
[deps.SpecialFunctions.weakdeps]
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
[[deps.StanBase]]
deps = ["CSV", "DataFrames", "DelimitedFiles", "Distributed", "DocStringExtensions", "JSON", "NamedTupleTools", "OrderedCollections", "Parameters", "Random", "Unicode"]
git-tree-sha1 = "fac478e96c6a32f08af58f709b07f5ee722b3140"
uuid = "d0ee94f6-a23d-54aa-bbe9-7f572d6da7f5"
version = "4.12.4"
[[deps.StanSample]]
deps = ["CSV", "CompatHelperLocal", "DataFrames", "DelimitedFiles", "DocStringExtensions", "JSON", "LazyArtifacts", "NamedTupleTools", "OrderedCollections", "Parameters", "Random", "Reexport", "Requires", "Serialization", "StanBase", "TableOperations", "Tables", "Unicode"]
git-tree-sha1 = "0e6f7c729879a416fd7b27cfd81b2b1f6e08586e"
uuid = "c1514b29-d3a0-5178-b312-660c88baa699"
version = "7.10.3"
[deps.StanSample.extensions]
AxisKeysExt = "AxisKeys"
InferenceObjectsExt = "InferenceObjects"
MCMCChainsExt = "MCMCChains"
MonteCarloMeasurementsExt = "MonteCarloMeasurements"
[deps.StanSample.weakdeps]
AxisKeys = "94b1ba4f-4ee9-5380-92f1-94cde586c3c5"
InferenceObjects = "b5cf5a8d-e756-4ee3-b014-01d49d192c00"
MCMCChains = "c7f686f2-ff18-58e9-bc7b-31028e88f75d"
MonteCarloMeasurements = "0987c9cc-fe09-11e8-30f0-b96dd679fdca"
[[deps.Statistics]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0"
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
version = "1.11.1"
weakdeps = ["SparseArrays"]
[deps.Statistics.extensions]
SparseArraysExt = ["SparseArrays"]
[[deps.StatsAPI]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "178ed29fd5b2a2cfc3bd31c13375ae925623ff36"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.8.0"
[[deps.StatsBase]]
deps = ["AliasTables", "DataAPI", "DataStructures", "IrrationalConstants", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "c6f18e5a52a176a383f6f6c635e0f81feed1d6d4"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.34.11"
[[deps.StatsFuns]]
deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
git-tree-sha1 = "3f4e1d24289cd974e089c617b1472311a2b1feab"
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
version = "2.1.0"
[deps.StatsFuns.extensions]
StatsFunsChainRulesCoreExt = "ChainRulesCore"
StatsFunsInverseFunctionsExt = "InverseFunctions"
[deps.StatsFuns.weakdeps]
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112"
[[deps.StringManipulation]]
deps = ["PrecompileTools"]
git-tree-sha1 = "d05693d339e37d6ab134c5ab53c29fce5ee5d7d5"
uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e"
version = "0.4.4"
[[deps.StyledStrings]]
uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
version = "1.11.0"
[[deps.SuiteSparse_jll]]
deps = ["Artifacts", "Libdl", "libblastrampoline_jll"]
uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c"
version = "7.7.0+0"
[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.3"
[[deps.TableOperations]]
deps = ["SentinelArrays", "Tables", "Test"]
git-tree-sha1 = "e383c87cf2a1dc41fa30c093b2a19877c83e1bc1"
uuid = "ab02a1b2-a7df-11e8-156e-fb1833f50b87"
version = "1.2.0"
[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"
[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
git-tree-sha1 = "f2c1efbc8f3a609aadf318094f8fc5204bdaf344"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.12.1"
[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
version = "1.10.0"
[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
version = "1.11.0"
[[deps.TranscodingStreams]]
git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.11.3"
[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
version = "1.11.0"
[[deps.UnPack]]
git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b"
uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed"
version = "1.0.2"
[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
version = "1.11.0"
[[deps.WeakRefStrings]]
deps = ["DataAPI", "InlineStrings", "Parsers"]
git-tree-sha1 = "0716e01c3b40413de5dedbc9c5c69f27cddfddfc"
uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5"
version = "1.4.3"
[[deps.WorkerUtilities]]
git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7"
uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60"
version = "1.6.1"
[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.13+1"
[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.11.0+0"
[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
version = "1.59.0+0"
[[deps.oneTBB_jll]]
deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"]
git-tree-sha1 = "da8c1f6eee04831f14edcfa5dae611d309807e57"
uuid = "1317d2d5-d96f-522e-a858-c73665f53c3e"
version = "2022.3.0+0"
[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
version = "17.4.0+2"
+10
View File
@@ -0,0 +1,10 @@
[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
StanSample = "c1514b29-d3a0-5178-b312-660c88baa699"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
StatsFuns = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
+64
View File
@@ -0,0 +1,64 @@
# party2d
Code and processed model inputs for generating two-dimensional party-position estimates from text and expert data. The model combines manifesto and media text indicators with expert survey placements in a Bayesian dynamic item-response framework.
## Repository contents
- `src/r/` — scripts that prepare processed model inputs from source datasets.
- `src/julia/` — Stan data preparation, model fitting, post-estimation, enrichment, and validation.
- `models/` — Stan model specification.
- `data/` — processed party-level inputs used by the model.
- `metadata/` — data dictionary and source-support documentation.
- `docs/` — raw data source documentation, coding decisions, and operational notes.
Processed inputs needed by the model are included in `data/` so the estimation step can be reproduced from the model-ready data.
## Running the pipeline
Run the full workflow with:
```bash
bash run_estimation.sh full
```
This executes the numbered workflow scripts:
```bash
bash scripts/01_prepare_data.sh
bash scripts/02_fit_model.sh
bash scripts/03_extract_estimates.sh
bash scripts/04_enrich_estimates.sh
```
The numbered scripts can also be run manually in that order. `scripts/05_validate_estimates.sh` runs validation checks after estimates have been generated.
The Bayesian model is computationally expensive. The production run used 4 cores on an AMD Ryzen 9 7945HX and took 60,372 seconds, approximately 16 hours 46 minutes.
If model output is already available, rebuild estimates without refitting Stan:
```bash
bash run_estimation.sh reuse
```
`reuse` reruns source-data processing, post-estimation, and enrichment while skipping the Stan fitting step.
To check the local setup without fitting the model, run:
```bash
bash run_estimation.sh dry-run
```
## Data inputs
The model-ready inputs are included under `data/`.
Original raw source files are not redistributed. See `docs/RAW_DATA_SOURCES.md` for the list of original data sources, access information, and expected local filenames for regenerating the processed inputs.
## Output variables
The two position variables are scaled from 0 to 1:
- `economic_lr`: economic left to economic right.
- `galtan`: cosmopolitan/socially liberal to traditionalist/nationalist.
Column definitions are in `metadata/data_dictionary.csv`.
+7
View File
@@ -0,0 +1,7 @@
# Data directory
This directory contains processed, model-ready party-level inputs used by the estimation pipeline.
Original raw source files are not stored here. To regenerate the processed inputs, place raw files in a local directory and set `PARTY2D_RAW_DATA_DIR`; see `../docs/RAW_DATA_SOURCES.md`.
Generated outputs and temporary staging files are ignored by git.
+1161
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+25079
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+48729
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2208
View File
File diff suppressed because it is too large Load Diff
+2035
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2634
View File
File diff suppressed because it is too large Load Diff
+150
View File
@@ -0,0 +1,150 @@
country,partyfacts_id,period,year_start,year_end,party_abbrev,party_name,lr_morgan,lr_morgan_se,n_surveys
BEL,302,1919-1939,1919,1939,COMM,Communist Party,0.005,0.01,11
BEL,2289,1919-1939,1919,1939,POB,Social Democratic Party,0.2511,0.016522821682861448,11
BEL,1892,1919-1939,1919,1939,CATH,Catholic Party,0.6785,0.02948780949970528,11
BEL,275,1919-1939,1919,1939,LIB,Liberal Party,0.7884,0.026152036249592493,10
BEL,1834,1919-1939,1919,1939,FNAT,Flemish Nationalist parties,0.8078,0.0359703034081272,11
BEL,1829,1919-1939,1919,1939,REX,Rexist,0.9872,0.01,10
BEL,302,1945-1973,1945,1973,COMM,Communist Party,0.005,0.01,17
BEL,480,1945-1973,1945,1973,PSB,Socialist Party,0.3193,0.01273312031440748,17
BEL,281,1945-1973,1945,1973,RW,Rassemblement Wallon,0.5365,0.024325,16
BEL,405,1945-1973,1945,1973,PSC,Christian Social Party,0.7090000000000001,0.01598309768989434,17
BEL,1333,1945-1973,1945,1973,FDF,Front Democratique Wallon,0.7189,0.033591184067532115,17
BEL,1424,1945-1973,1945,1973,VOLK,Volksunie,0.8321,0.01610436550241251,17
BEL,49,1945-1973,1945,1973,PLP,Liberal Party,0.995,0.01,17
DEU,1135,1919-1930,1919,1930,KPD,Communist Party,0.005,0.01,15
DEU,383,1919-1930,1919,1930,SDAP,Social Democratic Party,0.25579999999999997,0.011928788706318843,15
DEU,1826,1919-1930,1919,1930,DDP,Democratic Party,0.4557,0.012367726818889016,15
DEU,1798,1919-1930,1919,1930,DZP,Center Party,0.5125,0.01639562949894473,15
DEU,1825,1919-1930,1919,1930,BVP,Bavarian People's Party,0.6317,0.01697508869972809,11
DEU,1827,1919-1930,1919,1930,DVP,German People's Party,0.6517000000000001,0.012367726818889016,15
DEU,1828,1919-1930,1919,1930,RDMW,Economic Party,0.7064,0.017879622733461383,11
DEU,1831,1919-1930,1919,1930,LVP,Rural People's Party,0.8035,0.021766666666666667,9
DEU,1830,1919-1930,1919,1930,DNVP,German National People's Party,0.8817,0.01,15
DEU,1893,1919-1930,1919,1930,NAZI,National Socialist Party,0.995,0.01,15
DNK,379,1919-1939,1919,1939,SOCd,Social Democrats,0.005,0.01,19
DNK,1507,1919-1939,1919,1939,RAD,Radicals,0.3394,0.010599006904819953,19
DNK,1204,1919-1939,1919,1939,LIB,Liberals,0.7187,0.018330317136257882,19
DNK,536,1919-1939,1919,1939,CONS,Conservatives,0.995,0.01,19
DNK,277,1945-1973,1945,1973,COMM,Communists,0.005,0.01,22
DNK,1136,1945-1973,1945,1973,LS,Left Socialists,0.065,0.01,22
DNK,329,1945-1973,1945,1973,SOCL,Socialists (SF),0.1954,0.012642802479887697,22
DNK,379,1945-1973,1945,1973,SOCd,Social Democrats,0.41490000000000005,0.015329131505968391,22
DNK,1507,1945-1973,1945,1973,RAD,Radicals,0.6185,0.01328240462895453,22
DNK,1118,1945-1973,1945,1973,LC,Liberal Center,0.679,0.019492576934709162,18
DNK,1204,1945-1973,1945,1973,LIB,Liberals,0.8198000000000001,0.013772766276572434,22
DNK,1134,1945-1973,1945,1973,JUST,Justice Party,0.8408,0.03245126662986135,17
DNK,536,1945-1973,1945,1973,CONS,Conservatives,0.995,0.01,22
FIN,1904,1919-1939,1919,1939,SKDL,Communist,0.005,0.01,12
FIN,1303,1919-1939,1919,1939,SOCd,Social Democrats,0.2492,0.01,12
FIN,1862,1919-1939,1919,1939,PROG,National Progressives,0.575,0.013509996299037243,12
FIN,901,1919-1939,1919,1939,AGR,Agrarian Union,0.5932,0.012874911002928655,12
FIN,1229,1919-1939,1919,1939,SWPP,Swedish People's Party,0.6779000000000001,0.01896595634287921,12
FIN,495,1919-1939,1919,1939,CONS,Finnish Conservative Party,0.9196,0.011258330249197703,12
FIN,725,1919-1939,1919,1939,NPF,Finnish Patriotic Movement,0.995,0.01,12
FIN,1096,1945-1973,1945,1973,PDEM,Democratic League,0.005,0.01,15
FIN,1164,1945-1973,1945,1973,SDWS,Workers Smallholders SD League,0.2115,0.017376785279983945,15
FIN,1303,1945-1973,1945,1973,SOCd,Social Democrats,0.25780000000000003,0.01,15
FIN,901,1945-1973,1945,1973,CENT,Center Party,0.5776,0.012175669307143778,13
FIN,1689,1945-1973,1945,1973,FRP,Finnish Rural Party,0.6411,0.046899901590843125,13
FIN,249,1945-1973,1945,1973,LIB,Finnish People Party/Liberal League,0.7034,0.014200938936093862,15
FIN,1229,1945-1973,1945,1973,SWPP,Swedish People's Party,0.7975,0.01608579083124814,15
FIN,495,1945-1973,1945,1973,CONS,Finnish Conservative Party,0.9854999999999999,0.01,15
FRA,1251,1946-1958,1946,1958,PCF,Communist Party,0.005,0.01,23
FRA,1478,1946-1958,1946,1958,SFIO,Socialist Party,0.2365,0.01,23
FRA,737,1946-1958,1946,1958,MRP,Popular Republican Movement,0.43119999999999997,0.014033020066041133,23
FRA,14,1946-1958,1946,1958,RDA,Radical Party,0.4337,0.015048737488573584,20
FRA,2470,1946-1958,1946,1958,UDSR,Democratic Socialist Union,0.4462,0.014936934089698595,20
FRA,1083,1946-1958,1946,1958,RPF,Rally of the French People,0.7,0.01511729501913792,23
FRA,2689,1946-1958,1946,1958,AR,Republican Action,0.7293000000000001,0.01,20
FRA,3181,1946-1958,1946,1958,ARS,Republican Social Action,0.7737999999999999,0.017075,16
FRA,1246,1946-1958,1946,1958,RI,Independent Republicans,0.8262,0.01846796657658022,19
FRA,1629,1946-1958,1946,1958,CNIP,National Center of Independents,0.835,0.011700202427398648,19
FRA,1898,1946-1958,1946,1958,AP,Popular Action,0.8540000000000001,0.013085592354674942,11
FRA,1473,1946-1958,1946,1958,PRL,Republican Liberty Party,0.8756,0.013727516377056447,17
FRA,1580,1946-1958,1946,1958,POUJ,Poujadists,0.995,0.01,22
ISL,1249,1919-1939,1919,1939,COMM,Communist People Alliance,0.005,0.01,6
ISL,1325,1919-1939,1919,1939,SOCd,Social Democratic Party,0.2101,0.017350552344714183,6
ISL,964,1919-1939,1919,1939,PROG,Progressives/Farmers Party,0.6211,0.04992876592373045,6
ISL,1890,1919-1939,1919,1939,LIB,Liberal Party,0.6302,0.0441316401991436,6
ISL,363,1919-1939,1919,1939,INDP,Independence Party,0.8128,0.02135138559126004,6
ISL,1891,1919-1939,1919,1939,CONS,Conservative Party,0.995,0.01,6
ISL,1249,1945-1973,1945,1973,COMM,Communist People Alliance,0.005,0.01,9
ISL,1517,1945-1973,1945,1973,LLIB,Liberal Left,0.11410000000000001,0.012966666666666668,9
ISL,1325,1945-1973,1945,1973,SOCd,Social Democratic Party,0.3778,0.029333333333333336,9
ISL,964,1945-1973,1945,1973,PROG,Progressive Party,0.7299,0.025666666666666667,9
ISL,363,1945-1973,1945,1973,INDP,Independence Party,0.995,0.01,9
ISR,907,1949-1973,1949,1973,RAKA,Rakah,0.005,0.01,10
ISR,1417,1949-1973,1949,1973,MAKI,Maki,0.0346,0.01,10
ISR,169,1949-1973,1949,1973,MAPM,Mapam,0.1804,0.018499324311985015,10
ISR,1398,1949-1973,1949,1973,ADUT,Ahdut Haavoda,0.3492,0.03993956684792663,10
ISR,615,1949-1973,1949,1973,LAB,Labor Alignment,0.4808,0.021029146440119724,10
ISR,109,1949-1973,1949,1973,MAPI,Mapai,0.4827,0.02210432084457697,10
ISR,1491,1949-1973,1949,1973,PAUG,Poalei Agudat,0.5582,0.021392210420306423,6
ISR,396,1949-1973,1949,1973,RAFI,Rafi,0.6042000000000001,0.025361416138919145,7
ISR,712,1949-1973,1949,1973,PROG,Progressives,0.65,0.01902117241391813,8
ISR,434,1949-1973,1949,1973,ILIB,Independent Liberals,0.6579999999999999,0.025166666666666667,9
ISR,1447,1949-1973,1949,1973,NRP,National Religious Party,0.69,0.031030883234057557,7
ISR,909,1949-1973,1949,1973,URF,United Religious Front,0.77,0.04753880520164553,5
ISR,678,1949-1973,1949,1973,LIB,Liberals,0.7857,0.034499999999999996,9
ISR,770,1949-1973,1949,1973,NATL,National List,0.7967,0.04437658917342191,6
ISR,1786,1949-1973,1949,1973,TORA,Torah,0.8529000000000001,0.01,6
ISR,1489,1949-1973,1949,1973,ZION,General Zionists,0.8795000000000001,0.011166666666666667,9
ISR,198,1949-1973,1949,1973,GHAL,Gahal,0.9068999999999999,0.016652364696943193,8
ISR,641,1949-1973,1949,1973,AGDT,Agudat Israel,0.9625,0.012298373876248842,5
ISR,1655,1949-1973,1949,1973,HRUT,Herut,0.98,0.01,9
ITA,34,1946-1975,1946,1975,PCI,Communist Party,0.0308,0.01,12
ITA,1505,1946-1975,1946,1975,PSIU,Socialist Party of Proletarian Unity,0.0361,0.011547005383792516,12
ITA,742,1946-1975,1946,1975,PSI,Socialist Party (Nenni),0.2281,0.01,12
ITA,1126,1946-1975,1946,1975,PSDI,Social Democratic Party,0.36119999999999997,0.010883052574224445,12
ITA,394,1946-1975,1946,1975,PRI,Republican Party,0.4218,0.01,12
ITA,934,1946-1975,1946,1975,DC,Christian Democratic Party,0.5716,0.015703927321957824,12
ITA,1461,1946-1975,1946,1975,PLI,Liberal Party,0.7292000000000001,0.01,12
ITA,773,1946-1975,1946,1975,MON,Monarchists,0.9168000000000001,0.01,12
ITA,1696,1946-1975,1946,1975,MSI,Italian Social Movement,0.995,0.01,12
LUX,1647,1945-1973,1945,1973,COMM,Communist Party,0.005,0.01,7
LUX,186,1945-1973,1945,1973,SOCd,Socialist Party,0.36219999999999997,0.04849284188708385,7
LUX,539,1945-1973,1945,1973,CSOC,Christian Social Party,0.8576,0.05091181451434291,7
LUX,300,1945-1973,1945,1973,GRPD,Democratic Group,0.9333,0.051653170280245145,5
NLD,459,1919-1939,1919,1939,CPN,Communist Party,0.005,0.01,6
NLD,1894,1919-1939,1919,1939,SOCd,Social Democratic Party (SDAP),0.1832,0.022086232514094993,6
NLD,1581,1919-1939,1919,1939,RAD,Radical Party,0.4704,0.012818996320565301,6
NLD,1390,1919-1939,1919,1939,KVP,Catholic Party,0.5605,0.02629118990587278,6
NLD,1102,1919-1939,1919,1939,CHU,Christian Historical Union,0.6920000000000001,0.030986045246207204,6
NLD,1889,1919-1939,1919,1939,LIB,Liberal Party,0.7193999999999999,0.016738179909018384,6
NLD,163,1919-1939,1919,1939,ARP,Anti-Revolutionary Party,0.7243,0.03715059443221154,6
NLD,1178,1919-1939,1919,1939,SGP,Political Reformed Party,0.885,0.01,6
NLD,1832,1919-1939,1919,1939,NSB,National Social Movement,0.995,0.01,6
NLD,459,1945-1973,1945,1973,CPN,Communist Party,0.005,0.01,14
NLD,1050,1945-1973,1945,1973,PSP,Pacifist Socialist Party,0.0766,0.012427647748927736,14
NLD,1581,1945-1973,1945,1973,PPR,Political Radicals,0.1967,0.01501526495997263,11
NLD,1234,1945-1973,1945,1973,PVDA,Labor Party,0.2669,0.01312252697790004,14
NLD,921,1945-1973,1945,1973,DS70,Social Democratic splinter,0.4604,0.016436566377614103,14
NLD,1390,1945-1973,1945,1973,KVP,Catholic Party,0.5125,0.01,14
NLD,163,1945-1973,1945,1973,ARP,Anti-Revolutionary Party,0.5556,0.022931014556086014,14
NLD,1102,1945-1973,1945,1973,CHU,Christian Historical Union,0.6647,0.015100260168051978,14
NLD,828,1945-1973,1945,1973,VVD,Liberal Party,0.7609999999999999,0.0155546042793031,14
NLD,1178,1945-1973,1945,1973,SGP,Political Reform Party,0.9634999999999999,0.010650243767524398,13
NLD,1602,1945-1973,1945,1973,GPV,Reformed Political Association,0.9670000000000001,0.011200595222278741,12
NLD,1110,1945-1973,1945,1973,BP,Peasant Party (Poujadist),0.9682,0.013447405968168258,11
NOR,448,1919-1939,1919,1939,LAB,Labor Party,0.005,0.01,19
NOR,1173,1919-1939,1919,1939,LIB,Liberal Party,0.5771000000000001,0.017137355320130963,19
NOR,1072,1919-1939,1919,1939,AGR,Agrarian Party,0.7061,0.019821519406416535,19
NOR,503,1919-1939,1919,1939,CONS,Conservative Party,0.995,0.01,19
NOR,1079,1945-1973,1945,1973,COMM,Communist Party,0.005,0.01,20
NOR,1203,1945-1973,1945,1973,SOCL,Socialist People Party,0.0745,0.01,20
NOR,448,1945-1973,1945,1973,LAB,Labor Party,0.3529,0.011292143286373936,20
NOR,1173,1945-1973,1945,1973,LIB,Liberal Party,0.6426999999999999,0.015294704966098562,20
NOR,705,1945-1973,1945,1973,CHPP,Christian People Party,0.6907,0.020280350874157655,19
NOR,1072,1945-1973,1945,1973,CENT,Center Party,0.7813,0.01,20
NOR,503,1945-1973,1945,1973,CONS,Conservative Party,0.995,0.01,20
SWE,830,1919-1939,1919,1939,COMM,Communist Party,0.005,0.01,15
SWE,487,1919-1939,1919,1939,SOCd,Social Democratic Party,0.2585,0.013891100268397268,15
SWE,199,1919-1939,1919,1939,AGR,Agrarian Party,0.5857,0.014020199713270847,15
SWE,1274,1919-1939,1919,1939,LIB,Liberal Party,0.7419,0.011386571037849805,15
SWE,690,1919-1939,1919,1939,CONS,Conservative Party,0.995,0.01,15
SWE,830,1945-1973,1945,1973,COMM,Communist Party,0.005,0.01,20
SWE,487,1945-1973,1945,1973,SOCd,Social Democratic Party,0.3251,0.013796539421173702,20
SWE,199,1945-1973,1945,1973,CENT,Center/Agrarian Party,0.6456999999999999,0.01,19
SWE,1274,1945-1973,1945,1973,LIB,Liberal/People Party,0.7444,0.013863621460498696,20
SWE,690,1945-1973,1945,1973,CONS,Conservative Party,0.995,0.01,20
1 country partyfacts_id period year_start year_end party_abbrev party_name lr_morgan lr_morgan_se n_surveys
2 BEL 302 1919-1939 1919 1939 COMM Communist Party 0.005 0.01 11
3 BEL 2289 1919-1939 1919 1939 POB Social Democratic Party 0.2511 0.016522821682861448 11
4 BEL 1892 1919-1939 1919 1939 CATH Catholic Party 0.6785 0.02948780949970528 11
5 BEL 275 1919-1939 1919 1939 LIB Liberal Party 0.7884 0.026152036249592493 10
6 BEL 1834 1919-1939 1919 1939 FNAT Flemish Nationalist parties 0.8078 0.0359703034081272 11
7 BEL 1829 1919-1939 1919 1939 REX Rexist 0.9872 0.01 10
8 BEL 302 1945-1973 1945 1973 COMM Communist Party 0.005 0.01 17
9 BEL 480 1945-1973 1945 1973 PSB Socialist Party 0.3193 0.01273312031440748 17
10 BEL 281 1945-1973 1945 1973 RW Rassemblement Wallon 0.5365 0.024325 16
11 BEL 405 1945-1973 1945 1973 PSC Christian Social Party 0.7090000000000001 0.01598309768989434 17
12 BEL 1333 1945-1973 1945 1973 FDF Front Democratique Wallon 0.7189 0.033591184067532115 17
13 BEL 1424 1945-1973 1945 1973 VOLK Volksunie 0.8321 0.01610436550241251 17
14 BEL 49 1945-1973 1945 1973 PLP Liberal Party 0.995 0.01 17
15 DEU 1135 1919-1930 1919 1930 KPD Communist Party 0.005 0.01 15
16 DEU 383 1919-1930 1919 1930 SDAP Social Democratic Party 0.25579999999999997 0.011928788706318843 15
17 DEU 1826 1919-1930 1919 1930 DDP Democratic Party 0.4557 0.012367726818889016 15
18 DEU 1798 1919-1930 1919 1930 DZP Center Party 0.5125 0.01639562949894473 15
19 DEU 1825 1919-1930 1919 1930 BVP Bavarian People's Party 0.6317 0.01697508869972809 11
20 DEU 1827 1919-1930 1919 1930 DVP German People's Party 0.6517000000000001 0.012367726818889016 15
21 DEU 1828 1919-1930 1919 1930 RDMW Economic Party 0.7064 0.017879622733461383 11
22 DEU 1831 1919-1930 1919 1930 LVP Rural People's Party 0.8035 0.021766666666666667 9
23 DEU 1830 1919-1930 1919 1930 DNVP German National People's Party 0.8817 0.01 15
24 DEU 1893 1919-1930 1919 1930 NAZI National Socialist Party 0.995 0.01 15
25 DNK 379 1919-1939 1919 1939 SOCd Social Democrats 0.005 0.01 19
26 DNK 1507 1919-1939 1919 1939 RAD Radicals 0.3394 0.010599006904819953 19
27 DNK 1204 1919-1939 1919 1939 LIB Liberals 0.7187 0.018330317136257882 19
28 DNK 536 1919-1939 1919 1939 CONS Conservatives 0.995 0.01 19
29 DNK 277 1945-1973 1945 1973 COMM Communists 0.005 0.01 22
30 DNK 1136 1945-1973 1945 1973 LS Left Socialists 0.065 0.01 22
31 DNK 329 1945-1973 1945 1973 SOCL Socialists (SF) 0.1954 0.012642802479887697 22
32 DNK 379 1945-1973 1945 1973 SOCd Social Democrats 0.41490000000000005 0.015329131505968391 22
33 DNK 1507 1945-1973 1945 1973 RAD Radicals 0.6185 0.01328240462895453 22
34 DNK 1118 1945-1973 1945 1973 LC Liberal Center 0.679 0.019492576934709162 18
35 DNK 1204 1945-1973 1945 1973 LIB Liberals 0.8198000000000001 0.013772766276572434 22
36 DNK 1134 1945-1973 1945 1973 JUST Justice Party 0.8408 0.03245126662986135 17
37 DNK 536 1945-1973 1945 1973 CONS Conservatives 0.995 0.01 22
38 FIN 1904 1919-1939 1919 1939 SKDL Communist 0.005 0.01 12
39 FIN 1303 1919-1939 1919 1939 SOCd Social Democrats 0.2492 0.01 12
40 FIN 1862 1919-1939 1919 1939 PROG National Progressives 0.575 0.013509996299037243 12
41 FIN 901 1919-1939 1919 1939 AGR Agrarian Union 0.5932 0.012874911002928655 12
42 FIN 1229 1919-1939 1919 1939 SWPP Swedish People's Party 0.6779000000000001 0.01896595634287921 12
43 FIN 495 1919-1939 1919 1939 CONS Finnish Conservative Party 0.9196 0.011258330249197703 12
44 FIN 725 1919-1939 1919 1939 NPF Finnish Patriotic Movement 0.995 0.01 12
45 FIN 1096 1945-1973 1945 1973 PDEM Democratic League 0.005 0.01 15
46 FIN 1164 1945-1973 1945 1973 SDWS Workers Smallholders SD League 0.2115 0.017376785279983945 15
47 FIN 1303 1945-1973 1945 1973 SOCd Social Democrats 0.25780000000000003 0.01 15
48 FIN 901 1945-1973 1945 1973 CENT Center Party 0.5776 0.012175669307143778 13
49 FIN 1689 1945-1973 1945 1973 FRP Finnish Rural Party 0.6411 0.046899901590843125 13
50 FIN 249 1945-1973 1945 1973 LIB Finnish People Party/Liberal League 0.7034 0.014200938936093862 15
51 FIN 1229 1945-1973 1945 1973 SWPP Swedish People's Party 0.7975 0.01608579083124814 15
52 FIN 495 1945-1973 1945 1973 CONS Finnish Conservative Party 0.9854999999999999 0.01 15
53 FRA 1251 1946-1958 1946 1958 PCF Communist Party 0.005 0.01 23
54 FRA 1478 1946-1958 1946 1958 SFIO Socialist Party 0.2365 0.01 23
55 FRA 737 1946-1958 1946 1958 MRP Popular Republican Movement 0.43119999999999997 0.014033020066041133 23
56 FRA 14 1946-1958 1946 1958 RDA Radical Party 0.4337 0.015048737488573584 20
57 FRA 2470 1946-1958 1946 1958 UDSR Democratic Socialist Union 0.4462 0.014936934089698595 20
58 FRA 1083 1946-1958 1946 1958 RPF Rally of the French People 0.7 0.01511729501913792 23
59 FRA 2689 1946-1958 1946 1958 AR Republican Action 0.7293000000000001 0.01 20
60 FRA 3181 1946-1958 1946 1958 ARS Republican Social Action 0.7737999999999999 0.017075 16
61 FRA 1246 1946-1958 1946 1958 RI Independent Republicans 0.8262 0.01846796657658022 19
62 FRA 1629 1946-1958 1946 1958 CNIP National Center of Independents 0.835 0.011700202427398648 19
63 FRA 1898 1946-1958 1946 1958 AP Popular Action 0.8540000000000001 0.013085592354674942 11
64 FRA 1473 1946-1958 1946 1958 PRL Republican Liberty Party 0.8756 0.013727516377056447 17
65 FRA 1580 1946-1958 1946 1958 POUJ Poujadists 0.995 0.01 22
66 ISL 1249 1919-1939 1919 1939 COMM Communist People Alliance 0.005 0.01 6
67 ISL 1325 1919-1939 1919 1939 SOCd Social Democratic Party 0.2101 0.017350552344714183 6
68 ISL 964 1919-1939 1919 1939 PROG Progressives/Farmers Party 0.6211 0.04992876592373045 6
69 ISL 1890 1919-1939 1919 1939 LIB Liberal Party 0.6302 0.0441316401991436 6
70 ISL 363 1919-1939 1919 1939 INDP Independence Party 0.8128 0.02135138559126004 6
71 ISL 1891 1919-1939 1919 1939 CONS Conservative Party 0.995 0.01 6
72 ISL 1249 1945-1973 1945 1973 COMM Communist People Alliance 0.005 0.01 9
73 ISL 1517 1945-1973 1945 1973 LLIB Liberal Left 0.11410000000000001 0.012966666666666668 9
74 ISL 1325 1945-1973 1945 1973 SOCd Social Democratic Party 0.3778 0.029333333333333336 9
75 ISL 964 1945-1973 1945 1973 PROG Progressive Party 0.7299 0.025666666666666667 9
76 ISL 363 1945-1973 1945 1973 INDP Independence Party 0.995 0.01 9
77 ISR 907 1949-1973 1949 1973 RAKA Rakah 0.005 0.01 10
78 ISR 1417 1949-1973 1949 1973 MAKI Maki 0.0346 0.01 10
79 ISR 169 1949-1973 1949 1973 MAPM Mapam 0.1804 0.018499324311985015 10
80 ISR 1398 1949-1973 1949 1973 ADUT Ahdut Haavoda 0.3492 0.03993956684792663 10
81 ISR 615 1949-1973 1949 1973 LAB Labor Alignment 0.4808 0.021029146440119724 10
82 ISR 109 1949-1973 1949 1973 MAPI Mapai 0.4827 0.02210432084457697 10
83 ISR 1491 1949-1973 1949 1973 PAUG Poalei Agudat 0.5582 0.021392210420306423 6
84 ISR 396 1949-1973 1949 1973 RAFI Rafi 0.6042000000000001 0.025361416138919145 7
85 ISR 712 1949-1973 1949 1973 PROG Progressives 0.65 0.01902117241391813 8
86 ISR 434 1949-1973 1949 1973 ILIB Independent Liberals 0.6579999999999999 0.025166666666666667 9
87 ISR 1447 1949-1973 1949 1973 NRP National Religious Party 0.69 0.031030883234057557 7
88 ISR 909 1949-1973 1949 1973 URF United Religious Front 0.77 0.04753880520164553 5
89 ISR 678 1949-1973 1949 1973 LIB Liberals 0.7857 0.034499999999999996 9
90 ISR 770 1949-1973 1949 1973 NATL National List 0.7967 0.04437658917342191 6
91 ISR 1786 1949-1973 1949 1973 TORA Torah 0.8529000000000001 0.01 6
92 ISR 1489 1949-1973 1949 1973 ZION General Zionists 0.8795000000000001 0.011166666666666667 9
93 ISR 198 1949-1973 1949 1973 GHAL Gahal 0.9068999999999999 0.016652364696943193 8
94 ISR 641 1949-1973 1949 1973 AGDT Agudat Israel 0.9625 0.012298373876248842 5
95 ISR 1655 1949-1973 1949 1973 HRUT Herut 0.98 0.01 9
96 ITA 34 1946-1975 1946 1975 PCI Communist Party 0.0308 0.01 12
97 ITA 1505 1946-1975 1946 1975 PSIU Socialist Party of Proletarian Unity 0.0361 0.011547005383792516 12
98 ITA 742 1946-1975 1946 1975 PSI Socialist Party (Nenni) 0.2281 0.01 12
99 ITA 1126 1946-1975 1946 1975 PSDI Social Democratic Party 0.36119999999999997 0.010883052574224445 12
100 ITA 394 1946-1975 1946 1975 PRI Republican Party 0.4218 0.01 12
101 ITA 934 1946-1975 1946 1975 DC Christian Democratic Party 0.5716 0.015703927321957824 12
102 ITA 1461 1946-1975 1946 1975 PLI Liberal Party 0.7292000000000001 0.01 12
103 ITA 773 1946-1975 1946 1975 MON Monarchists 0.9168000000000001 0.01 12
104 ITA 1696 1946-1975 1946 1975 MSI Italian Social Movement 0.995 0.01 12
105 LUX 1647 1945-1973 1945 1973 COMM Communist Party 0.005 0.01 7
106 LUX 186 1945-1973 1945 1973 SOCd Socialist Party 0.36219999999999997 0.04849284188708385 7
107 LUX 539 1945-1973 1945 1973 CSOC Christian Social Party 0.8576 0.05091181451434291 7
108 LUX 300 1945-1973 1945 1973 GRPD Democratic Group 0.9333 0.051653170280245145 5
109 NLD 459 1919-1939 1919 1939 CPN Communist Party 0.005 0.01 6
110 NLD 1894 1919-1939 1919 1939 SOCd Social Democratic Party (SDAP) 0.1832 0.022086232514094993 6
111 NLD 1581 1919-1939 1919 1939 RAD Radical Party 0.4704 0.012818996320565301 6
112 NLD 1390 1919-1939 1919 1939 KVP Catholic Party 0.5605 0.02629118990587278 6
113 NLD 1102 1919-1939 1919 1939 CHU Christian Historical Union 0.6920000000000001 0.030986045246207204 6
114 NLD 1889 1919-1939 1919 1939 LIB Liberal Party 0.7193999999999999 0.016738179909018384 6
115 NLD 163 1919-1939 1919 1939 ARP Anti-Revolutionary Party 0.7243 0.03715059443221154 6
116 NLD 1178 1919-1939 1919 1939 SGP Political Reformed Party 0.885 0.01 6
117 NLD 1832 1919-1939 1919 1939 NSB National Social Movement 0.995 0.01 6
118 NLD 459 1945-1973 1945 1973 CPN Communist Party 0.005 0.01 14
119 NLD 1050 1945-1973 1945 1973 PSP Pacifist Socialist Party 0.0766 0.012427647748927736 14
120 NLD 1581 1945-1973 1945 1973 PPR Political Radicals 0.1967 0.01501526495997263 11
121 NLD 1234 1945-1973 1945 1973 PVDA Labor Party 0.2669 0.01312252697790004 14
122 NLD 921 1945-1973 1945 1973 DS70 Social Democratic splinter 0.4604 0.016436566377614103 14
123 NLD 1390 1945-1973 1945 1973 KVP Catholic Party 0.5125 0.01 14
124 NLD 163 1945-1973 1945 1973 ARP Anti-Revolutionary Party 0.5556 0.022931014556086014 14
125 NLD 1102 1945-1973 1945 1973 CHU Christian Historical Union 0.6647 0.015100260168051978 14
126 NLD 828 1945-1973 1945 1973 VVD Liberal Party 0.7609999999999999 0.0155546042793031 14
127 NLD 1178 1945-1973 1945 1973 SGP Political Reform Party 0.9634999999999999 0.010650243767524398 13
128 NLD 1602 1945-1973 1945 1973 GPV Reformed Political Association 0.9670000000000001 0.011200595222278741 12
129 NLD 1110 1945-1973 1945 1973 BP Peasant Party (Poujadist) 0.9682 0.013447405968168258 11
130 NOR 448 1919-1939 1919 1939 LAB Labor Party 0.005 0.01 19
131 NOR 1173 1919-1939 1919 1939 LIB Liberal Party 0.5771000000000001 0.017137355320130963 19
132 NOR 1072 1919-1939 1919 1939 AGR Agrarian Party 0.7061 0.019821519406416535 19
133 NOR 503 1919-1939 1919 1939 CONS Conservative Party 0.995 0.01 19
134 NOR 1079 1945-1973 1945 1973 COMM Communist Party 0.005 0.01 20
135 NOR 1203 1945-1973 1945 1973 SOCL Socialist People Party 0.0745 0.01 20
136 NOR 448 1945-1973 1945 1973 LAB Labor Party 0.3529 0.011292143286373936 20
137 NOR 1173 1945-1973 1945 1973 LIB Liberal Party 0.6426999999999999 0.015294704966098562 20
138 NOR 705 1945-1973 1945 1973 CHPP Christian People Party 0.6907 0.020280350874157655 19
139 NOR 1072 1945-1973 1945 1973 CENT Center Party 0.7813 0.01 20
140 NOR 503 1945-1973 1945 1973 CONS Conservative Party 0.995 0.01 20
141 SWE 830 1919-1939 1919 1939 COMM Communist Party 0.005 0.01 15
142 SWE 487 1919-1939 1919 1939 SOCd Social Democratic Party 0.2585 0.013891100268397268 15
143 SWE 199 1919-1939 1919 1939 AGR Agrarian Party 0.5857 0.014020199713270847 15
144 SWE 1274 1919-1939 1919 1939 LIB Liberal Party 0.7419 0.011386571037849805 15
145 SWE 690 1919-1939 1919 1939 CONS Conservative Party 0.995 0.01 15
146 SWE 830 1945-1973 1945 1973 COMM Communist Party 0.005 0.01 20
147 SWE 487 1945-1973 1945 1973 SOCd Social Democratic Party 0.3251 0.013796539421173702 20
148 SWE 199 1945-1973 1945 1973 CENT Center/Agrarian Party 0.6456999999999999 0.01 19
149 SWE 1274 1945-1973 1945 1973 LIB Liberal/People Party 0.7444 0.013863621460498696 20
150 SWE 690 1945-1973 1945 1973 CONS Conservative Party 0.995 0.01 20
+472
View File
@@ -0,0 +1,472 @@
country,party,var,year,val,project,n_scale,val_int,n_experts
BE,49,lr_morgan,1971,0.995,Morgan,10,10,17
BE,281,lr_morgan,1968,0.5365,Morgan,10,5,16
BE,281,lr_morgan,1971,0.5365,Morgan,10,5,16
BE,405,lr_morgan,1946,0.7090000000000001,Morgan,10,7,17
BE,405,lr_morgan,1949,0.7090000000000001,Morgan,10,7,17
BE,405,lr_morgan,1950,0.7090000000000001,Morgan,10,7,17
BE,405,lr_morgan,1954,0.7090000000000001,Morgan,10,7,17
BE,405,lr_morgan,1958,0.7090000000000001,Morgan,10,7,17
BE,405,lr_morgan,1961,0.7090000000000001,Morgan,10,7,17
BE,405,lr_morgan,1965,0.7090000000000001,Morgan,10,7,17
BE,480,lr_morgan,1946,0.3193,Morgan,10,3,17
BE,480,lr_morgan,1949,0.3193,Morgan,10,3,17
BE,480,lr_morgan,1950,0.3193,Morgan,10,3,17
BE,480,lr_morgan,1954,0.3193,Morgan,10,3,17
BE,480,lr_morgan,1958,0.3193,Morgan,10,3,17
BE,480,lr_morgan,1961,0.3193,Morgan,10,3,17
BE,480,lr_morgan,1965,0.3193,Morgan,10,3,17
BE,480,lr_morgan,1968,0.3193,Morgan,10,3,17
BE,480,lr_morgan,1971,0.3193,Morgan,10,3,17
BE,1424,lr_morgan,1954,0.8321,Morgan,10,8,17
BE,1424,lr_morgan,1958,0.8321,Morgan,10,8,17
BE,1424,lr_morgan,1961,0.8321,Morgan,10,8,17
BE,1424,lr_morgan,1965,0.8321,Morgan,10,8,17
BE,1424,lr_morgan,1968,0.8321,Morgan,10,8,17
BE,1424,lr_morgan,1971,0.8321,Morgan,10,8,17
DK,277,lr_morgan,1945,0.005,Morgan,10,0,22
DK,277,lr_morgan,1947,0.005,Morgan,10,0,22
DK,277,lr_morgan,1950,0.005,Morgan,10,0,22
DK,277,lr_morgan,1953,0.005,Morgan,10,0,22
DK,277,lr_morgan,1957,0.005,Morgan,10,0,22
DK,277,lr_morgan,1960,0.005,Morgan,10,0,22
DK,277,lr_morgan,1964,0.005,Morgan,10,0,22
DK,277,lr_morgan,1966,0.005,Morgan,10,0,22
DK,277,lr_morgan,1968,0.005,Morgan,10,0,22
DK,277,lr_morgan,1971,0.005,Morgan,10,0,22
DK,277,lr_morgan,1973,0.005,Morgan,10,0,22
DK,329,lr_morgan,1960,0.1954,Morgan,10,2,22
DK,329,lr_morgan,1964,0.1954,Morgan,10,2,22
DK,329,lr_morgan,1966,0.1954,Morgan,10,2,22
DK,329,lr_morgan,1968,0.1954,Morgan,10,2,22
DK,329,lr_morgan,1971,0.1954,Morgan,10,2,22
DK,329,lr_morgan,1973,0.1954,Morgan,10,2,22
DK,379,lr_morgan,1945,0.41490000000000005,Morgan,10,4,22
DK,379,lr_morgan,1947,0.41490000000000005,Morgan,10,4,22
DK,379,lr_morgan,1950,0.41490000000000005,Morgan,10,4,22
DK,379,lr_morgan,1953,0.41490000000000005,Morgan,10,4,22
DK,379,lr_morgan,1957,0.41490000000000005,Morgan,10,4,22
DK,379,lr_morgan,1960,0.41490000000000005,Morgan,10,4,22
DK,379,lr_morgan,1964,0.41490000000000005,Morgan,10,4,22
DK,379,lr_morgan,1966,0.41490000000000005,Morgan,10,4,22
DK,379,lr_morgan,1968,0.41490000000000005,Morgan,10,4,22
DK,379,lr_morgan,1971,0.41490000000000005,Morgan,10,4,22
DK,379,lr_morgan,1973,0.41490000000000005,Morgan,10,4,22
DK,536,lr_morgan,1945,0.995,Morgan,10,10,22
DK,536,lr_morgan,1947,0.995,Morgan,10,10,22
DK,536,lr_morgan,1950,0.995,Morgan,10,10,22
DK,536,lr_morgan,1953,0.995,Morgan,10,10,22
DK,536,lr_morgan,1957,0.995,Morgan,10,10,22
DK,536,lr_morgan,1960,0.995,Morgan,10,10,22
DK,536,lr_morgan,1964,0.995,Morgan,10,10,22
DK,536,lr_morgan,1966,0.995,Morgan,10,10,22
DK,536,lr_morgan,1968,0.995,Morgan,10,10,22
DK,536,lr_morgan,1971,0.995,Morgan,10,10,22
DK,536,lr_morgan,1973,0.995,Morgan,10,10,22
DK,1134,lr_morgan,1945,0.8408,Morgan,10,8,17
DK,1134,lr_morgan,1947,0.8408,Morgan,10,8,17
DK,1134,lr_morgan,1950,0.8408,Morgan,10,8,17
DK,1134,lr_morgan,1953,0.8408,Morgan,10,8,17
DK,1134,lr_morgan,1957,0.8408,Morgan,10,8,17
DK,1134,lr_morgan,1960,0.8408,Morgan,10,8,17
DK,1134,lr_morgan,1964,0.8408,Morgan,10,8,17
DK,1134,lr_morgan,1966,0.8408,Morgan,10,8,17
DK,1134,lr_morgan,1968,0.8408,Morgan,10,8,17
DK,1134,lr_morgan,1971,0.8408,Morgan,10,8,17
DK,1134,lr_morgan,1973,0.8408,Morgan,10,8,17
DK,1136,lr_morgan,1968,0.065,Morgan,10,1,22
DK,1136,lr_morgan,1971,0.065,Morgan,10,1,22
DK,1136,lr_morgan,1973,0.065,Morgan,10,1,22
DK,1204,lr_morgan,1945,0.8198000000000001,Morgan,10,8,22
DK,1204,lr_morgan,1947,0.8198000000000001,Morgan,10,8,22
DK,1204,lr_morgan,1950,0.8198000000000001,Morgan,10,8,22
DK,1204,lr_morgan,1953,0.8198000000000001,Morgan,10,8,22
DK,1204,lr_morgan,1957,0.8198000000000001,Morgan,10,8,22
DK,1204,lr_morgan,1960,0.8198000000000001,Morgan,10,8,22
DK,1204,lr_morgan,1964,0.8198000000000001,Morgan,10,8,22
DK,1204,lr_morgan,1966,0.8198000000000001,Morgan,10,8,22
DK,1204,lr_morgan,1968,0.8198000000000001,Morgan,10,8,22
DK,1204,lr_morgan,1971,0.8198000000000001,Morgan,10,8,22
DK,1204,lr_morgan,1973,0.8198000000000001,Morgan,10,8,22
DK,1507,lr_morgan,1945,0.6185,Morgan,10,6,22
DK,1507,lr_morgan,1947,0.6185,Morgan,10,6,22
DK,1507,lr_morgan,1950,0.6185,Morgan,10,6,22
DK,1507,lr_morgan,1953,0.6185,Morgan,10,6,22
DK,1507,lr_morgan,1957,0.6185,Morgan,10,6,22
DK,1507,lr_morgan,1960,0.6185,Morgan,10,6,22
DK,1507,lr_morgan,1964,0.6185,Morgan,10,6,22
DK,1507,lr_morgan,1966,0.6185,Morgan,10,6,22
DK,1507,lr_morgan,1968,0.6185,Morgan,10,6,22
DK,1507,lr_morgan,1971,0.6185,Morgan,10,6,22
DK,1507,lr_morgan,1973,0.6185,Morgan,10,6,22
FI,249,lr_morgan,1945,0.7034,Morgan,10,7,15
FI,249,lr_morgan,1948,0.7034,Morgan,10,7,15
FI,249,lr_morgan,1951,0.7034,Morgan,10,7,15
FI,249,lr_morgan,1954,0.7034,Morgan,10,7,15
FI,249,lr_morgan,1958,0.7034,Morgan,10,7,15
FI,249,lr_morgan,1962,0.7034,Morgan,10,7,15
FI,249,lr_morgan,1966,0.7034,Morgan,10,7,15
FI,249,lr_morgan,1970,0.7034,Morgan,10,7,15
FI,249,lr_morgan,1972,0.7034,Morgan,10,7,15
FI,495,lr_morgan,1945,0.9854999999999999,Morgan,10,10,15
FI,495,lr_morgan,1948,0.9854999999999999,Morgan,10,10,15
FI,495,lr_morgan,1951,0.9854999999999999,Morgan,10,10,15
FI,495,lr_morgan,1954,0.9854999999999999,Morgan,10,10,15
FI,495,lr_morgan,1958,0.9854999999999999,Morgan,10,10,15
FI,495,lr_morgan,1962,0.9854999999999999,Morgan,10,10,15
FI,495,lr_morgan,1966,0.9854999999999999,Morgan,10,10,15
FI,495,lr_morgan,1970,0.9854999999999999,Morgan,10,10,15
FI,495,lr_morgan,1972,0.9854999999999999,Morgan,10,10,15
FI,901,lr_morgan,1945,0.5776,Morgan,10,6,13
FI,901,lr_morgan,1948,0.5776,Morgan,10,6,13
FI,901,lr_morgan,1951,0.5776,Morgan,10,6,13
FI,901,lr_morgan,1954,0.5776,Morgan,10,6,13
FI,901,lr_morgan,1958,0.5776,Morgan,10,6,13
FI,901,lr_morgan,1962,0.5776,Morgan,10,6,13
FI,901,lr_morgan,1966,0.5776,Morgan,10,6,13
FI,901,lr_morgan,1970,0.5776,Morgan,10,6,13
FI,901,lr_morgan,1972,0.5776,Morgan,10,6,13
FI,1096,lr_morgan,1945,0.005,Morgan,10,0,15
FI,1096,lr_morgan,1948,0.005,Morgan,10,0,15
FI,1096,lr_morgan,1951,0.005,Morgan,10,0,15
FI,1096,lr_morgan,1954,0.005,Morgan,10,0,15
FI,1096,lr_morgan,1958,0.005,Morgan,10,0,15
FI,1096,lr_morgan,1962,0.005,Morgan,10,0,15
FI,1096,lr_morgan,1966,0.005,Morgan,10,0,15
FI,1096,lr_morgan,1970,0.005,Morgan,10,0,15
FI,1096,lr_morgan,1972,0.005,Morgan,10,0,15
FI,1164,lr_morgan,1958,0.2115,Morgan,10,2,15
FI,1164,lr_morgan,1962,0.2115,Morgan,10,2,15
FI,1164,lr_morgan,1966,0.2115,Morgan,10,2,15
FI,1229,lr_morgan,1945,0.7975,Morgan,10,8,15
FI,1229,lr_morgan,1948,0.7975,Morgan,10,8,15
FI,1229,lr_morgan,1951,0.7975,Morgan,10,8,15
FI,1229,lr_morgan,1954,0.7975,Morgan,10,8,15
FI,1229,lr_morgan,1958,0.7975,Morgan,10,8,15
FI,1229,lr_morgan,1962,0.7975,Morgan,10,8,15
FI,1229,lr_morgan,1966,0.7975,Morgan,10,8,15
FI,1229,lr_morgan,1970,0.7975,Morgan,10,8,15
FI,1229,lr_morgan,1972,0.7975,Morgan,10,8,15
FI,1303,lr_morgan,1945,0.25780000000000003,Morgan,10,3,15
FI,1303,lr_morgan,1948,0.25780000000000003,Morgan,10,3,15
FI,1303,lr_morgan,1951,0.25780000000000003,Morgan,10,3,15
FI,1303,lr_morgan,1954,0.25780000000000003,Morgan,10,3,15
FI,1303,lr_morgan,1958,0.25780000000000003,Morgan,10,3,15
FI,1303,lr_morgan,1962,0.25780000000000003,Morgan,10,3,15
FI,1303,lr_morgan,1966,0.25780000000000003,Morgan,10,3,15
FI,1303,lr_morgan,1970,0.25780000000000003,Morgan,10,3,15
FI,1303,lr_morgan,1972,0.25780000000000003,Morgan,10,3,15
FI,1689,lr_morgan,1966,0.6411,Morgan,10,6,13
FI,1689,lr_morgan,1970,0.6411,Morgan,10,6,13
FI,1689,lr_morgan,1972,0.6411,Morgan,10,6,13
FR,14,lr_morgan,1946,0.4337,Morgan,10,4,20
FR,14,lr_morgan,1951,0.4337,Morgan,10,4,20
FR,14,lr_morgan,1956,0.4337,Morgan,10,4,20
FR,14,lr_morgan,1958,0.4337,Morgan,10,4,20
FR,737,lr_morgan,1946,0.43119999999999997,Morgan,10,4,23
FR,737,lr_morgan,1951,0.43119999999999997,Morgan,10,4,23
FR,737,lr_morgan,1956,0.43119999999999997,Morgan,10,4,23
FR,737,lr_morgan,1958,0.43119999999999997,Morgan,10,4,23
FR,1083,lr_morgan,1946,0.7,Morgan,10,7,23
FR,1083,lr_morgan,1951,0.7,Morgan,10,7,23
FR,1083,lr_morgan,1956,0.7,Morgan,10,7,23
FR,1083,lr_morgan,1958,0.7,Morgan,10,7,23
FR,1251,lr_morgan,1946,0.005,Morgan,10,0,23
FR,1251,lr_morgan,1951,0.005,Morgan,10,0,23
FR,1251,lr_morgan,1956,0.005,Morgan,10,0,23
FR,1251,lr_morgan,1958,0.005,Morgan,10,0,23
FR,1478,lr_morgan,1946,0.2365,Morgan,10,2,23
FR,1478,lr_morgan,1951,0.2365,Morgan,10,2,23
FR,1478,lr_morgan,1956,0.2365,Morgan,10,2,23
FR,1478,lr_morgan,1958,0.2365,Morgan,10,2,23
FR,1629,lr_morgan,1946,0.835,Morgan,10,8,19
FR,1629,lr_morgan,1951,0.835,Morgan,10,8,19
FR,1629,lr_morgan,1956,0.835,Morgan,10,8,19
FR,1629,lr_morgan,1958,0.835,Morgan,10,8,19
IL,109,lr_morgan,1949,0.4827,Morgan,10,5,10
IL,109,lr_morgan,1951,0.4827,Morgan,10,5,10
IL,109,lr_morgan,1955,0.4827,Morgan,10,5,10
IL,109,lr_morgan,1959,0.4827,Morgan,10,5,10
IL,109,lr_morgan,1961,0.4827,Morgan,10,5,10
IL,169,lr_morgan,1949,0.1804,Morgan,10,2,10
IL,169,lr_morgan,1951,0.1804,Morgan,10,2,10
IL,169,lr_morgan,1955,0.1804,Morgan,10,2,10
IL,169,lr_morgan,1959,0.1804,Morgan,10,2,10
IL,169,lr_morgan,1961,0.1804,Morgan,10,2,10
IL,169,lr_morgan,1965,0.1804,Morgan,10,2,10
IL,434,lr_morgan,1965,0.6579999999999999,Morgan,10,7,9
IL,434,lr_morgan,1969,0.6579999999999999,Morgan,10,7,9
IL,434,lr_morgan,1973,0.6579999999999999,Morgan,10,7,9
IL,615,lr_morgan,1965,0.4808,Morgan,10,5,10
IL,615,lr_morgan,1969,0.4808,Morgan,10,5,10
IL,615,lr_morgan,1973,0.4808,Morgan,10,5,10
IL,641,lr_morgan,1949,0.9625,Morgan,10,10,5
IL,641,lr_morgan,1951,0.9625,Morgan,10,10,5
IL,641,lr_morgan,1955,0.9625,Morgan,10,10,5
IL,641,lr_morgan,1959,0.9625,Morgan,10,10,5
IL,641,lr_morgan,1965,0.9625,Morgan,10,10,5
IL,641,lr_morgan,1969,0.9625,Morgan,10,10,5
IL,907,lr_morgan,1965,0.005,Morgan,10,0,10
IL,907,lr_morgan,1969,0.005,Morgan,10,0,10
IL,907,lr_morgan,1973,0.005,Morgan,10,0,10
IL,1398,lr_morgan,1955,0.3492,Morgan,10,3,10
IL,1398,lr_morgan,1959,0.3492,Morgan,10,3,10
IL,1398,lr_morgan,1961,0.3492,Morgan,10,3,10
IL,1417,lr_morgan,1949,0.0346,Morgan,10,0,10
IL,1417,lr_morgan,1951,0.0346,Morgan,10,0,10
IL,1417,lr_morgan,1955,0.0346,Morgan,10,0,10
IL,1417,lr_morgan,1959,0.0346,Morgan,10,0,10
IL,1417,lr_morgan,1961,0.0346,Morgan,10,0,10
IL,1417,lr_morgan,1965,0.0346,Morgan,10,0,10
IL,1417,lr_morgan,1969,0.0346,Morgan,10,0,10
IL,1447,lr_morgan,1955,0.69,Morgan,10,7,7
IL,1447,lr_morgan,1959,0.69,Morgan,10,7,7
IL,1447,lr_morgan,1961,0.69,Morgan,10,7,7
IL,1447,lr_morgan,1965,0.69,Morgan,10,7,7
IL,1447,lr_morgan,1969,0.69,Morgan,10,7,7
IL,1447,lr_morgan,1973,0.69,Morgan,10,7,7
IL,1489,lr_morgan,1949,0.8795000000000001,Morgan,10,9,9
IL,1489,lr_morgan,1951,0.8795000000000001,Morgan,10,9,9
IL,1489,lr_morgan,1955,0.8795000000000001,Morgan,10,9,9
IL,1489,lr_morgan,1959,0.8795000000000001,Morgan,10,9,9
IL,1655,lr_morgan,1949,0.98,Morgan,10,10,9
IL,1655,lr_morgan,1951,0.98,Morgan,10,10,9
IL,1655,lr_morgan,1955,0.98,Morgan,10,10,9
IL,1655,lr_morgan,1959,0.98,Morgan,10,10,9
IL,1655,lr_morgan,1961,0.98,Morgan,10,10,9
IS,363,lr_morgan,1946,0.995,Morgan,10,10,9
IS,363,lr_morgan,1949,0.995,Morgan,10,10,9
IS,363,lr_morgan,1953,0.995,Morgan,10,10,9
IS,363,lr_morgan,1956,0.995,Morgan,10,10,9
IS,363,lr_morgan,1959,0.995,Morgan,10,10,9
IS,363,lr_morgan,1963,0.995,Morgan,10,10,9
IS,363,lr_morgan,1967,0.995,Morgan,10,10,9
IS,363,lr_morgan,1971,0.995,Morgan,10,10,9
IS,964,lr_morgan,1946,0.7299,Morgan,10,7,9
IS,964,lr_morgan,1949,0.7299,Morgan,10,7,9
IS,964,lr_morgan,1953,0.7299,Morgan,10,7,9
IS,964,lr_morgan,1956,0.7299,Morgan,10,7,9
IS,964,lr_morgan,1959,0.7299,Morgan,10,7,9
IS,964,lr_morgan,1963,0.7299,Morgan,10,7,9
IS,964,lr_morgan,1967,0.7299,Morgan,10,7,9
IS,964,lr_morgan,1971,0.7299,Morgan,10,7,9
IS,1249,lr_morgan,1946,0.005,Morgan,10,0,9
IS,1249,lr_morgan,1949,0.005,Morgan,10,0,9
IS,1249,lr_morgan,1953,0.005,Morgan,10,0,9
IS,1249,lr_morgan,1956,0.005,Morgan,10,0,9
IS,1249,lr_morgan,1959,0.005,Morgan,10,0,9
IS,1249,lr_morgan,1963,0.005,Morgan,10,0,9
IS,1249,lr_morgan,1967,0.005,Morgan,10,0,9
IS,1249,lr_morgan,1971,0.005,Morgan,10,0,9
IS,1325,lr_morgan,1946,0.3778,Morgan,10,4,9
IS,1325,lr_morgan,1949,0.3778,Morgan,10,4,9
IS,1325,lr_morgan,1953,0.3778,Morgan,10,4,9
IS,1325,lr_morgan,1956,0.3778,Morgan,10,4,9
IS,1325,lr_morgan,1959,0.3778,Morgan,10,4,9
IS,1325,lr_morgan,1963,0.3778,Morgan,10,4,9
IS,1325,lr_morgan,1967,0.3778,Morgan,10,4,9
IS,1325,lr_morgan,1971,0.3778,Morgan,10,4,9
IS,1517,lr_morgan,1967,0.11410000000000001,Morgan,10,1,9
IS,1517,lr_morgan,1971,0.11410000000000001,Morgan,10,1,9
IT,394,lr_morgan,1946,0.4218,Morgan,10,4,12
IT,394,lr_morgan,1948,0.4218,Morgan,10,4,12
IT,394,lr_morgan,1953,0.4218,Morgan,10,4,12
IT,394,lr_morgan,1958,0.4218,Morgan,10,4,12
IT,394,lr_morgan,1963,0.4218,Morgan,10,4,12
IT,394,lr_morgan,1968,0.4218,Morgan,10,4,12
IT,394,lr_morgan,1972,0.4218,Morgan,10,4,12
IT,742,lr_morgan,1946,0.2281,Morgan,10,2,12
IT,742,lr_morgan,1948,0.2281,Morgan,10,2,12
IT,742,lr_morgan,1953,0.2281,Morgan,10,2,12
IT,742,lr_morgan,1958,0.2281,Morgan,10,2,12
IT,742,lr_morgan,1963,0.2281,Morgan,10,2,12
IT,742,lr_morgan,1972,0.2281,Morgan,10,2,12
IT,934,lr_morgan,1946,0.5716,Morgan,10,6,12
IT,934,lr_morgan,1948,0.5716,Morgan,10,6,12
IT,934,lr_morgan,1953,0.5716,Morgan,10,6,12
IT,934,lr_morgan,1958,0.5716,Morgan,10,6,12
IT,934,lr_morgan,1963,0.5716,Morgan,10,6,12
IT,934,lr_morgan,1968,0.5716,Morgan,10,6,12
IT,934,lr_morgan,1972,0.5716,Morgan,10,6,12
IT,1126,lr_morgan,1948,0.36119999999999997,Morgan,10,4,12
IT,1126,lr_morgan,1953,0.36119999999999997,Morgan,10,4,12
IT,1126,lr_morgan,1958,0.36119999999999997,Morgan,10,4,12
IT,1126,lr_morgan,1963,0.36119999999999997,Morgan,10,4,12
IT,1126,lr_morgan,1972,0.36119999999999997,Morgan,10,4,12
IT,1461,lr_morgan,1946,0.7292000000000001,Morgan,10,7,12
IT,1461,lr_morgan,1948,0.7292000000000001,Morgan,10,7,12
IT,1461,lr_morgan,1953,0.7292000000000001,Morgan,10,7,12
IT,1461,lr_morgan,1958,0.7292000000000001,Morgan,10,7,12
IT,1461,lr_morgan,1963,0.7292000000000001,Morgan,10,7,12
IT,1461,lr_morgan,1968,0.7292000000000001,Morgan,10,7,12
IT,1461,lr_morgan,1972,0.7292000000000001,Morgan,10,7,12
LU,186,lr_morgan,1945,0.36219999999999997,Morgan,10,4,7
LU,186,lr_morgan,1948,0.36219999999999997,Morgan,10,4,7
LU,186,lr_morgan,1951,0.36219999999999997,Morgan,10,4,7
LU,186,lr_morgan,1954,0.36219999999999997,Morgan,10,4,7
LU,186,lr_morgan,1959,0.36219999999999997,Morgan,10,4,7
LU,186,lr_morgan,1964,0.36219999999999997,Morgan,10,4,7
LU,186,lr_morgan,1968,0.36219999999999997,Morgan,10,4,7
LU,300,lr_morgan,1945,0.9333,Morgan,10,9,5
LU,300,lr_morgan,1948,0.9333,Morgan,10,9,5
LU,300,lr_morgan,1951,0.9333,Morgan,10,9,5
LU,300,lr_morgan,1954,0.9333,Morgan,10,9,5
LU,300,lr_morgan,1959,0.9333,Morgan,10,9,5
LU,300,lr_morgan,1964,0.9333,Morgan,10,9,5
LU,300,lr_morgan,1968,0.9333,Morgan,10,9,5
LU,539,lr_morgan,1945,0.8576,Morgan,10,9,7
LU,539,lr_morgan,1948,0.8576,Morgan,10,9,7
LU,539,lr_morgan,1951,0.8576,Morgan,10,9,7
LU,539,lr_morgan,1954,0.8576,Morgan,10,9,7
LU,539,lr_morgan,1959,0.8576,Morgan,10,9,7
LU,539,lr_morgan,1964,0.8576,Morgan,10,9,7
LU,539,lr_morgan,1968,0.8576,Morgan,10,9,7
LU,1647,lr_morgan,1945,0.005,Morgan,10,0,7
LU,1647,lr_morgan,1948,0.005,Morgan,10,0,7
LU,1647,lr_morgan,1951,0.005,Morgan,10,0,7
LU,1647,lr_morgan,1954,0.005,Morgan,10,0,7
LU,1647,lr_morgan,1959,0.005,Morgan,10,0,7
LU,1647,lr_morgan,1964,0.005,Morgan,10,0,7
LU,1647,lr_morgan,1968,0.005,Morgan,10,0,7
NL,163,lr_morgan,1946,0.5556,Morgan,10,6,14
NL,163,lr_morgan,1948,0.5556,Morgan,10,6,14
NL,163,lr_morgan,1952,0.5556,Morgan,10,6,14
NL,163,lr_morgan,1956,0.5556,Morgan,10,6,14
NL,163,lr_morgan,1959,0.5556,Morgan,10,6,14
NL,163,lr_morgan,1963,0.5556,Morgan,10,6,14
NL,163,lr_morgan,1967,0.5556,Morgan,10,6,14
NL,163,lr_morgan,1971,0.5556,Morgan,10,6,14
NL,163,lr_morgan,1972,0.5556,Morgan,10,6,14
NL,459,lr_morgan,1946,0.005,Morgan,10,0,14
NL,459,lr_morgan,1948,0.005,Morgan,10,0,14
NL,828,lr_morgan,1946,0.7609999999999999,Morgan,10,8,14
NL,828,lr_morgan,1948,0.7609999999999999,Morgan,10,8,14
NL,828,lr_morgan,1952,0.7609999999999999,Morgan,10,8,14
NL,828,lr_morgan,1956,0.7609999999999999,Morgan,10,8,14
NL,828,lr_morgan,1959,0.7609999999999999,Morgan,10,8,14
NL,828,lr_morgan,1963,0.7609999999999999,Morgan,10,8,14
NL,828,lr_morgan,1967,0.7609999999999999,Morgan,10,8,14
NL,828,lr_morgan,1971,0.7609999999999999,Morgan,10,8,14
NL,828,lr_morgan,1972,0.7609999999999999,Morgan,10,8,14
NL,921,lr_morgan,1971,0.4604,Morgan,10,5,14
NL,921,lr_morgan,1972,0.4604,Morgan,10,5,14
NL,1102,lr_morgan,1946,0.6647,Morgan,10,7,14
NL,1102,lr_morgan,1948,0.6647,Morgan,10,7,14
NL,1102,lr_morgan,1952,0.6647,Morgan,10,7,14
NL,1102,lr_morgan,1956,0.6647,Morgan,10,7,14
NL,1102,lr_morgan,1959,0.6647,Morgan,10,7,14
NL,1102,lr_morgan,1963,0.6647,Morgan,10,7,14
NL,1102,lr_morgan,1967,0.6647,Morgan,10,7,14
NL,1102,lr_morgan,1971,0.6647,Morgan,10,7,14
NL,1102,lr_morgan,1972,0.6647,Morgan,10,7,14
NL,1178,lr_morgan,1946,0.9634999999999999,Morgan,10,10,13
NL,1178,lr_morgan,1948,0.9634999999999999,Morgan,10,10,13
NL,1234,lr_morgan,1946,0.2669,Morgan,10,3,14
NL,1234,lr_morgan,1948,0.2669,Morgan,10,3,14
NL,1234,lr_morgan,1952,0.2669,Morgan,10,3,14
NL,1234,lr_morgan,1956,0.2669,Morgan,10,3,14
NL,1234,lr_morgan,1959,0.2669,Morgan,10,3,14
NL,1234,lr_morgan,1963,0.2669,Morgan,10,3,14
NL,1234,lr_morgan,1967,0.2669,Morgan,10,3,14
NL,1234,lr_morgan,1971,0.2669,Morgan,10,3,14
NL,1234,lr_morgan,1972,0.2669,Morgan,10,3,14
NL,1390,lr_morgan,1946,0.5125,Morgan,10,5,14
NL,1390,lr_morgan,1948,0.5125,Morgan,10,5,14
NL,1390,lr_morgan,1952,0.5125,Morgan,10,5,14
NL,1390,lr_morgan,1956,0.5125,Morgan,10,5,14
NL,1390,lr_morgan,1959,0.5125,Morgan,10,5,14
NL,1390,lr_morgan,1963,0.5125,Morgan,10,5,14
NL,1390,lr_morgan,1967,0.5125,Morgan,10,5,14
NL,1390,lr_morgan,1971,0.5125,Morgan,10,5,14
NL,1390,lr_morgan,1972,0.5125,Morgan,10,5,14
NL,1581,lr_morgan,1971,0.1967,Morgan,10,2,11
NL,1581,lr_morgan,1972,0.1967,Morgan,10,2,11
NO,448,lr_morgan,1945,0.3529,Morgan,10,4,20
NO,448,lr_morgan,1949,0.3529,Morgan,10,4,20
NO,448,lr_morgan,1953,0.3529,Morgan,10,4,20
NO,448,lr_morgan,1957,0.3529,Morgan,10,4,20
NO,448,lr_morgan,1961,0.3529,Morgan,10,4,20
NO,448,lr_morgan,1965,0.3529,Morgan,10,4,20
NO,448,lr_morgan,1969,0.3529,Morgan,10,4,20
NO,448,lr_morgan,1973,0.3529,Morgan,10,4,20
NO,503,lr_morgan,1945,0.995,Morgan,10,10,20
NO,503,lr_morgan,1949,0.995,Morgan,10,10,20
NO,503,lr_morgan,1953,0.995,Morgan,10,10,20
NO,503,lr_morgan,1957,0.995,Morgan,10,10,20
NO,503,lr_morgan,1961,0.995,Morgan,10,10,20
NO,503,lr_morgan,1965,0.995,Morgan,10,10,20
NO,503,lr_morgan,1969,0.995,Morgan,10,10,20
NO,503,lr_morgan,1973,0.995,Morgan,10,10,20
NO,705,lr_morgan,1945,0.6907,Morgan,10,7,19
NO,705,lr_morgan,1949,0.6907,Morgan,10,7,19
NO,705,lr_morgan,1953,0.6907,Morgan,10,7,19
NO,705,lr_morgan,1957,0.6907,Morgan,10,7,19
NO,705,lr_morgan,1961,0.6907,Morgan,10,7,19
NO,705,lr_morgan,1965,0.6907,Morgan,10,7,19
NO,705,lr_morgan,1969,0.6907,Morgan,10,7,19
NO,705,lr_morgan,1973,0.6907,Morgan,10,7,19
NO,1072,lr_morgan,1945,0.7813,Morgan,10,8,20
NO,1072,lr_morgan,1949,0.7813,Morgan,10,8,20
NO,1072,lr_morgan,1953,0.7813,Morgan,10,8,20
NO,1072,lr_morgan,1957,0.7813,Morgan,10,8,20
NO,1072,lr_morgan,1961,0.7813,Morgan,10,8,20
NO,1072,lr_morgan,1965,0.7813,Morgan,10,8,20
NO,1072,lr_morgan,1969,0.7813,Morgan,10,8,20
NO,1072,lr_morgan,1973,0.7813,Morgan,10,8,20
NO,1079,lr_morgan,1945,0.005,Morgan,10,0,20
NO,1079,lr_morgan,1949,0.005,Morgan,10,0,20
NO,1079,lr_morgan,1953,0.005,Morgan,10,0,20
NO,1079,lr_morgan,1957,0.005,Morgan,10,0,20
NO,1173,lr_morgan,1945,0.6426999999999999,Morgan,10,6,20
NO,1173,lr_morgan,1949,0.6426999999999999,Morgan,10,6,20
NO,1173,lr_morgan,1953,0.6426999999999999,Morgan,10,6,20
NO,1173,lr_morgan,1957,0.6426999999999999,Morgan,10,6,20
NO,1173,lr_morgan,1961,0.6426999999999999,Morgan,10,6,20
NO,1173,lr_morgan,1965,0.6426999999999999,Morgan,10,6,20
NO,1173,lr_morgan,1969,0.6426999999999999,Morgan,10,6,20
NO,1173,lr_morgan,1973,0.6426999999999999,Morgan,10,6,20
SE,199,lr_morgan,1948,0.6456999999999999,Morgan,10,6,19
SE,199,lr_morgan,1952,0.6456999999999999,Morgan,10,6,19
SE,199,lr_morgan,1956,0.6456999999999999,Morgan,10,6,19
SE,199,lr_morgan,1958,0.6456999999999999,Morgan,10,6,19
SE,199,lr_morgan,1960,0.6456999999999999,Morgan,10,6,19
SE,199,lr_morgan,1964,0.6456999999999999,Morgan,10,6,19
SE,199,lr_morgan,1968,0.6456999999999999,Morgan,10,6,19
SE,199,lr_morgan,1970,0.6456999999999999,Morgan,10,6,19
SE,199,lr_morgan,1973,0.6456999999999999,Morgan,10,6,19
SE,487,lr_morgan,1948,0.3251,Morgan,10,3,20
SE,487,lr_morgan,1952,0.3251,Morgan,10,3,20
SE,487,lr_morgan,1956,0.3251,Morgan,10,3,20
SE,487,lr_morgan,1958,0.3251,Morgan,10,3,20
SE,487,lr_morgan,1960,0.3251,Morgan,10,3,20
SE,487,lr_morgan,1964,0.3251,Morgan,10,3,20
SE,487,lr_morgan,1968,0.3251,Morgan,10,3,20
SE,487,lr_morgan,1970,0.3251,Morgan,10,3,20
SE,487,lr_morgan,1973,0.3251,Morgan,10,3,20
SE,690,lr_morgan,1948,0.995,Morgan,10,10,20
SE,690,lr_morgan,1952,0.995,Morgan,10,10,20
SE,690,lr_morgan,1956,0.995,Morgan,10,10,20
SE,690,lr_morgan,1958,0.995,Morgan,10,10,20
SE,690,lr_morgan,1960,0.995,Morgan,10,10,20
SE,690,lr_morgan,1964,0.995,Morgan,10,10,20
SE,690,lr_morgan,1968,0.995,Morgan,10,10,20
SE,690,lr_morgan,1970,0.995,Morgan,10,10,20
SE,690,lr_morgan,1973,0.995,Morgan,10,10,20
SE,830,lr_morgan,1948,0.005,Morgan,10,0,20
SE,830,lr_morgan,1952,0.005,Morgan,10,0,20
SE,830,lr_morgan,1956,0.005,Morgan,10,0,20
SE,830,lr_morgan,1958,0.005,Morgan,10,0,20
SE,830,lr_morgan,1960,0.005,Morgan,10,0,20
SE,830,lr_morgan,1964,0.005,Morgan,10,0,20
SE,830,lr_morgan,1968,0.005,Morgan,10,0,20
SE,830,lr_morgan,1970,0.005,Morgan,10,0,20
SE,830,lr_morgan,1973,0.005,Morgan,10,0,20
SE,1274,lr_morgan,1948,0.7444,Morgan,10,7,20
SE,1274,lr_morgan,1952,0.7444,Morgan,10,7,20
SE,1274,lr_morgan,1956,0.7444,Morgan,10,7,20
SE,1274,lr_morgan,1958,0.7444,Morgan,10,7,20
SE,1274,lr_morgan,1960,0.7444,Morgan,10,7,20
SE,1274,lr_morgan,1964,0.7444,Morgan,10,7,20
SE,1274,lr_morgan,1968,0.7444,Morgan,10,7,20
SE,1274,lr_morgan,1970,0.7444,Morgan,10,7,20
SE,1274,lr_morgan,1973,0.7444,Morgan,10,7,20
1 country party var year val project n_scale val_int n_experts
2 BE 49 lr_morgan 1971 0.995 Morgan 10 10 17
3 BE 281 lr_morgan 1968 0.5365 Morgan 10 5 16
4 BE 281 lr_morgan 1971 0.5365 Morgan 10 5 16
5 BE 405 lr_morgan 1946 0.7090000000000001 Morgan 10 7 17
6 BE 405 lr_morgan 1949 0.7090000000000001 Morgan 10 7 17
7 BE 405 lr_morgan 1950 0.7090000000000001 Morgan 10 7 17
8 BE 405 lr_morgan 1954 0.7090000000000001 Morgan 10 7 17
9 BE 405 lr_morgan 1958 0.7090000000000001 Morgan 10 7 17
10 BE 405 lr_morgan 1961 0.7090000000000001 Morgan 10 7 17
11 BE 405 lr_morgan 1965 0.7090000000000001 Morgan 10 7 17
12 BE 480 lr_morgan 1946 0.3193 Morgan 10 3 17
13 BE 480 lr_morgan 1949 0.3193 Morgan 10 3 17
14 BE 480 lr_morgan 1950 0.3193 Morgan 10 3 17
15 BE 480 lr_morgan 1954 0.3193 Morgan 10 3 17
16 BE 480 lr_morgan 1958 0.3193 Morgan 10 3 17
17 BE 480 lr_morgan 1961 0.3193 Morgan 10 3 17
18 BE 480 lr_morgan 1965 0.3193 Morgan 10 3 17
19 BE 480 lr_morgan 1968 0.3193 Morgan 10 3 17
20 BE 480 lr_morgan 1971 0.3193 Morgan 10 3 17
21 BE 1424 lr_morgan 1954 0.8321 Morgan 10 8 17
22 BE 1424 lr_morgan 1958 0.8321 Morgan 10 8 17
23 BE 1424 lr_morgan 1961 0.8321 Morgan 10 8 17
24 BE 1424 lr_morgan 1965 0.8321 Morgan 10 8 17
25 BE 1424 lr_morgan 1968 0.8321 Morgan 10 8 17
26 BE 1424 lr_morgan 1971 0.8321 Morgan 10 8 17
27 DK 277 lr_morgan 1945 0.005 Morgan 10 0 22
28 DK 277 lr_morgan 1947 0.005 Morgan 10 0 22
29 DK 277 lr_morgan 1950 0.005 Morgan 10 0 22
30 DK 277 lr_morgan 1953 0.005 Morgan 10 0 22
31 DK 277 lr_morgan 1957 0.005 Morgan 10 0 22
32 DK 277 lr_morgan 1960 0.005 Morgan 10 0 22
33 DK 277 lr_morgan 1964 0.005 Morgan 10 0 22
34 DK 277 lr_morgan 1966 0.005 Morgan 10 0 22
35 DK 277 lr_morgan 1968 0.005 Morgan 10 0 22
36 DK 277 lr_morgan 1971 0.005 Morgan 10 0 22
37 DK 277 lr_morgan 1973 0.005 Morgan 10 0 22
38 DK 329 lr_morgan 1960 0.1954 Morgan 10 2 22
39 DK 329 lr_morgan 1964 0.1954 Morgan 10 2 22
40 DK 329 lr_morgan 1966 0.1954 Morgan 10 2 22
41 DK 329 lr_morgan 1968 0.1954 Morgan 10 2 22
42 DK 329 lr_morgan 1971 0.1954 Morgan 10 2 22
43 DK 329 lr_morgan 1973 0.1954 Morgan 10 2 22
44 DK 379 lr_morgan 1945 0.41490000000000005 Morgan 10 4 22
45 DK 379 lr_morgan 1947 0.41490000000000005 Morgan 10 4 22
46 DK 379 lr_morgan 1950 0.41490000000000005 Morgan 10 4 22
47 DK 379 lr_morgan 1953 0.41490000000000005 Morgan 10 4 22
48 DK 379 lr_morgan 1957 0.41490000000000005 Morgan 10 4 22
49 DK 379 lr_morgan 1960 0.41490000000000005 Morgan 10 4 22
50 DK 379 lr_morgan 1964 0.41490000000000005 Morgan 10 4 22
51 DK 379 lr_morgan 1966 0.41490000000000005 Morgan 10 4 22
52 DK 379 lr_morgan 1968 0.41490000000000005 Morgan 10 4 22
53 DK 379 lr_morgan 1971 0.41490000000000005 Morgan 10 4 22
54 DK 379 lr_morgan 1973 0.41490000000000005 Morgan 10 4 22
55 DK 536 lr_morgan 1945 0.995 Morgan 10 10 22
56 DK 536 lr_morgan 1947 0.995 Morgan 10 10 22
57 DK 536 lr_morgan 1950 0.995 Morgan 10 10 22
58 DK 536 lr_morgan 1953 0.995 Morgan 10 10 22
59 DK 536 lr_morgan 1957 0.995 Morgan 10 10 22
60 DK 536 lr_morgan 1960 0.995 Morgan 10 10 22
61 DK 536 lr_morgan 1964 0.995 Morgan 10 10 22
62 DK 536 lr_morgan 1966 0.995 Morgan 10 10 22
63 DK 536 lr_morgan 1968 0.995 Morgan 10 10 22
64 DK 536 lr_morgan 1971 0.995 Morgan 10 10 22
65 DK 536 lr_morgan 1973 0.995 Morgan 10 10 22
66 DK 1134 lr_morgan 1945 0.8408 Morgan 10 8 17
67 DK 1134 lr_morgan 1947 0.8408 Morgan 10 8 17
68 DK 1134 lr_morgan 1950 0.8408 Morgan 10 8 17
69 DK 1134 lr_morgan 1953 0.8408 Morgan 10 8 17
70 DK 1134 lr_morgan 1957 0.8408 Morgan 10 8 17
71 DK 1134 lr_morgan 1960 0.8408 Morgan 10 8 17
72 DK 1134 lr_morgan 1964 0.8408 Morgan 10 8 17
73 DK 1134 lr_morgan 1966 0.8408 Morgan 10 8 17
74 DK 1134 lr_morgan 1968 0.8408 Morgan 10 8 17
75 DK 1134 lr_morgan 1971 0.8408 Morgan 10 8 17
76 DK 1134 lr_morgan 1973 0.8408 Morgan 10 8 17
77 DK 1136 lr_morgan 1968 0.065 Morgan 10 1 22
78 DK 1136 lr_morgan 1971 0.065 Morgan 10 1 22
79 DK 1136 lr_morgan 1973 0.065 Morgan 10 1 22
80 DK 1204 lr_morgan 1945 0.8198000000000001 Morgan 10 8 22
81 DK 1204 lr_morgan 1947 0.8198000000000001 Morgan 10 8 22
82 DK 1204 lr_morgan 1950 0.8198000000000001 Morgan 10 8 22
83 DK 1204 lr_morgan 1953 0.8198000000000001 Morgan 10 8 22
84 DK 1204 lr_morgan 1957 0.8198000000000001 Morgan 10 8 22
85 DK 1204 lr_morgan 1960 0.8198000000000001 Morgan 10 8 22
86 DK 1204 lr_morgan 1964 0.8198000000000001 Morgan 10 8 22
87 DK 1204 lr_morgan 1966 0.8198000000000001 Morgan 10 8 22
88 DK 1204 lr_morgan 1968 0.8198000000000001 Morgan 10 8 22
89 DK 1204 lr_morgan 1971 0.8198000000000001 Morgan 10 8 22
90 DK 1204 lr_morgan 1973 0.8198000000000001 Morgan 10 8 22
91 DK 1507 lr_morgan 1945 0.6185 Morgan 10 6 22
92 DK 1507 lr_morgan 1947 0.6185 Morgan 10 6 22
93 DK 1507 lr_morgan 1950 0.6185 Morgan 10 6 22
94 DK 1507 lr_morgan 1953 0.6185 Morgan 10 6 22
95 DK 1507 lr_morgan 1957 0.6185 Morgan 10 6 22
96 DK 1507 lr_morgan 1960 0.6185 Morgan 10 6 22
97 DK 1507 lr_morgan 1964 0.6185 Morgan 10 6 22
98 DK 1507 lr_morgan 1966 0.6185 Morgan 10 6 22
99 DK 1507 lr_morgan 1968 0.6185 Morgan 10 6 22
100 DK 1507 lr_morgan 1971 0.6185 Morgan 10 6 22
101 DK 1507 lr_morgan 1973 0.6185 Morgan 10 6 22
102 FI 249 lr_morgan 1945 0.7034 Morgan 10 7 15
103 FI 249 lr_morgan 1948 0.7034 Morgan 10 7 15
104 FI 249 lr_morgan 1951 0.7034 Morgan 10 7 15
105 FI 249 lr_morgan 1954 0.7034 Morgan 10 7 15
106 FI 249 lr_morgan 1958 0.7034 Morgan 10 7 15
107 FI 249 lr_morgan 1962 0.7034 Morgan 10 7 15
108 FI 249 lr_morgan 1966 0.7034 Morgan 10 7 15
109 FI 249 lr_morgan 1970 0.7034 Morgan 10 7 15
110 FI 249 lr_morgan 1972 0.7034 Morgan 10 7 15
111 FI 495 lr_morgan 1945 0.9854999999999999 Morgan 10 10 15
112 FI 495 lr_morgan 1948 0.9854999999999999 Morgan 10 10 15
113 FI 495 lr_morgan 1951 0.9854999999999999 Morgan 10 10 15
114 FI 495 lr_morgan 1954 0.9854999999999999 Morgan 10 10 15
115 FI 495 lr_morgan 1958 0.9854999999999999 Morgan 10 10 15
116 FI 495 lr_morgan 1962 0.9854999999999999 Morgan 10 10 15
117 FI 495 lr_morgan 1966 0.9854999999999999 Morgan 10 10 15
118 FI 495 lr_morgan 1970 0.9854999999999999 Morgan 10 10 15
119 FI 495 lr_morgan 1972 0.9854999999999999 Morgan 10 10 15
120 FI 901 lr_morgan 1945 0.5776 Morgan 10 6 13
121 FI 901 lr_morgan 1948 0.5776 Morgan 10 6 13
122 FI 901 lr_morgan 1951 0.5776 Morgan 10 6 13
123 FI 901 lr_morgan 1954 0.5776 Morgan 10 6 13
124 FI 901 lr_morgan 1958 0.5776 Morgan 10 6 13
125 FI 901 lr_morgan 1962 0.5776 Morgan 10 6 13
126 FI 901 lr_morgan 1966 0.5776 Morgan 10 6 13
127 FI 901 lr_morgan 1970 0.5776 Morgan 10 6 13
128 FI 901 lr_morgan 1972 0.5776 Morgan 10 6 13
129 FI 1096 lr_morgan 1945 0.005 Morgan 10 0 15
130 FI 1096 lr_morgan 1948 0.005 Morgan 10 0 15
131 FI 1096 lr_morgan 1951 0.005 Morgan 10 0 15
132 FI 1096 lr_morgan 1954 0.005 Morgan 10 0 15
133 FI 1096 lr_morgan 1958 0.005 Morgan 10 0 15
134 FI 1096 lr_morgan 1962 0.005 Morgan 10 0 15
135 FI 1096 lr_morgan 1966 0.005 Morgan 10 0 15
136 FI 1096 lr_morgan 1970 0.005 Morgan 10 0 15
137 FI 1096 lr_morgan 1972 0.005 Morgan 10 0 15
138 FI 1164 lr_morgan 1958 0.2115 Morgan 10 2 15
139 FI 1164 lr_morgan 1962 0.2115 Morgan 10 2 15
140 FI 1164 lr_morgan 1966 0.2115 Morgan 10 2 15
141 FI 1229 lr_morgan 1945 0.7975 Morgan 10 8 15
142 FI 1229 lr_morgan 1948 0.7975 Morgan 10 8 15
143 FI 1229 lr_morgan 1951 0.7975 Morgan 10 8 15
144 FI 1229 lr_morgan 1954 0.7975 Morgan 10 8 15
145 FI 1229 lr_morgan 1958 0.7975 Morgan 10 8 15
146 FI 1229 lr_morgan 1962 0.7975 Morgan 10 8 15
147 FI 1229 lr_morgan 1966 0.7975 Morgan 10 8 15
148 FI 1229 lr_morgan 1970 0.7975 Morgan 10 8 15
149 FI 1229 lr_morgan 1972 0.7975 Morgan 10 8 15
150 FI 1303 lr_morgan 1945 0.25780000000000003 Morgan 10 3 15
151 FI 1303 lr_morgan 1948 0.25780000000000003 Morgan 10 3 15
152 FI 1303 lr_morgan 1951 0.25780000000000003 Morgan 10 3 15
153 FI 1303 lr_morgan 1954 0.25780000000000003 Morgan 10 3 15
154 FI 1303 lr_morgan 1958 0.25780000000000003 Morgan 10 3 15
155 FI 1303 lr_morgan 1962 0.25780000000000003 Morgan 10 3 15
156 FI 1303 lr_morgan 1966 0.25780000000000003 Morgan 10 3 15
157 FI 1303 lr_morgan 1970 0.25780000000000003 Morgan 10 3 15
158 FI 1303 lr_morgan 1972 0.25780000000000003 Morgan 10 3 15
159 FI 1689 lr_morgan 1966 0.6411 Morgan 10 6 13
160 FI 1689 lr_morgan 1970 0.6411 Morgan 10 6 13
161 FI 1689 lr_morgan 1972 0.6411 Morgan 10 6 13
162 FR 14 lr_morgan 1946 0.4337 Morgan 10 4 20
163 FR 14 lr_morgan 1951 0.4337 Morgan 10 4 20
164 FR 14 lr_morgan 1956 0.4337 Morgan 10 4 20
165 FR 14 lr_morgan 1958 0.4337 Morgan 10 4 20
166 FR 737 lr_morgan 1946 0.43119999999999997 Morgan 10 4 23
167 FR 737 lr_morgan 1951 0.43119999999999997 Morgan 10 4 23
168 FR 737 lr_morgan 1956 0.43119999999999997 Morgan 10 4 23
169 FR 737 lr_morgan 1958 0.43119999999999997 Morgan 10 4 23
170 FR 1083 lr_morgan 1946 0.7 Morgan 10 7 23
171 FR 1083 lr_morgan 1951 0.7 Morgan 10 7 23
172 FR 1083 lr_morgan 1956 0.7 Morgan 10 7 23
173 FR 1083 lr_morgan 1958 0.7 Morgan 10 7 23
174 FR 1251 lr_morgan 1946 0.005 Morgan 10 0 23
175 FR 1251 lr_morgan 1951 0.005 Morgan 10 0 23
176 FR 1251 lr_morgan 1956 0.005 Morgan 10 0 23
177 FR 1251 lr_morgan 1958 0.005 Morgan 10 0 23
178 FR 1478 lr_morgan 1946 0.2365 Morgan 10 2 23
179 FR 1478 lr_morgan 1951 0.2365 Morgan 10 2 23
180 FR 1478 lr_morgan 1956 0.2365 Morgan 10 2 23
181 FR 1478 lr_morgan 1958 0.2365 Morgan 10 2 23
182 FR 1629 lr_morgan 1946 0.835 Morgan 10 8 19
183 FR 1629 lr_morgan 1951 0.835 Morgan 10 8 19
184 FR 1629 lr_morgan 1956 0.835 Morgan 10 8 19
185 FR 1629 lr_morgan 1958 0.835 Morgan 10 8 19
186 IL 109 lr_morgan 1949 0.4827 Morgan 10 5 10
187 IL 109 lr_morgan 1951 0.4827 Morgan 10 5 10
188 IL 109 lr_morgan 1955 0.4827 Morgan 10 5 10
189 IL 109 lr_morgan 1959 0.4827 Morgan 10 5 10
190 IL 109 lr_morgan 1961 0.4827 Morgan 10 5 10
191 IL 169 lr_morgan 1949 0.1804 Morgan 10 2 10
192 IL 169 lr_morgan 1951 0.1804 Morgan 10 2 10
193 IL 169 lr_morgan 1955 0.1804 Morgan 10 2 10
194 IL 169 lr_morgan 1959 0.1804 Morgan 10 2 10
195 IL 169 lr_morgan 1961 0.1804 Morgan 10 2 10
196 IL 169 lr_morgan 1965 0.1804 Morgan 10 2 10
197 IL 434 lr_morgan 1965 0.6579999999999999 Morgan 10 7 9
198 IL 434 lr_morgan 1969 0.6579999999999999 Morgan 10 7 9
199 IL 434 lr_morgan 1973 0.6579999999999999 Morgan 10 7 9
200 IL 615 lr_morgan 1965 0.4808 Morgan 10 5 10
201 IL 615 lr_morgan 1969 0.4808 Morgan 10 5 10
202 IL 615 lr_morgan 1973 0.4808 Morgan 10 5 10
203 IL 641 lr_morgan 1949 0.9625 Morgan 10 10 5
204 IL 641 lr_morgan 1951 0.9625 Morgan 10 10 5
205 IL 641 lr_morgan 1955 0.9625 Morgan 10 10 5
206 IL 641 lr_morgan 1959 0.9625 Morgan 10 10 5
207 IL 641 lr_morgan 1965 0.9625 Morgan 10 10 5
208 IL 641 lr_morgan 1969 0.9625 Morgan 10 10 5
209 IL 907 lr_morgan 1965 0.005 Morgan 10 0 10
210 IL 907 lr_morgan 1969 0.005 Morgan 10 0 10
211 IL 907 lr_morgan 1973 0.005 Morgan 10 0 10
212 IL 1398 lr_morgan 1955 0.3492 Morgan 10 3 10
213 IL 1398 lr_morgan 1959 0.3492 Morgan 10 3 10
214 IL 1398 lr_morgan 1961 0.3492 Morgan 10 3 10
215 IL 1417 lr_morgan 1949 0.0346 Morgan 10 0 10
216 IL 1417 lr_morgan 1951 0.0346 Morgan 10 0 10
217 IL 1417 lr_morgan 1955 0.0346 Morgan 10 0 10
218 IL 1417 lr_morgan 1959 0.0346 Morgan 10 0 10
219 IL 1417 lr_morgan 1961 0.0346 Morgan 10 0 10
220 IL 1417 lr_morgan 1965 0.0346 Morgan 10 0 10
221 IL 1417 lr_morgan 1969 0.0346 Morgan 10 0 10
222 IL 1447 lr_morgan 1955 0.69 Morgan 10 7 7
223 IL 1447 lr_morgan 1959 0.69 Morgan 10 7 7
224 IL 1447 lr_morgan 1961 0.69 Morgan 10 7 7
225 IL 1447 lr_morgan 1965 0.69 Morgan 10 7 7
226 IL 1447 lr_morgan 1969 0.69 Morgan 10 7 7
227 IL 1447 lr_morgan 1973 0.69 Morgan 10 7 7
228 IL 1489 lr_morgan 1949 0.8795000000000001 Morgan 10 9 9
229 IL 1489 lr_morgan 1951 0.8795000000000001 Morgan 10 9 9
230 IL 1489 lr_morgan 1955 0.8795000000000001 Morgan 10 9 9
231 IL 1489 lr_morgan 1959 0.8795000000000001 Morgan 10 9 9
232 IL 1655 lr_morgan 1949 0.98 Morgan 10 10 9
233 IL 1655 lr_morgan 1951 0.98 Morgan 10 10 9
234 IL 1655 lr_morgan 1955 0.98 Morgan 10 10 9
235 IL 1655 lr_morgan 1959 0.98 Morgan 10 10 9
236 IL 1655 lr_morgan 1961 0.98 Morgan 10 10 9
237 IS 363 lr_morgan 1946 0.995 Morgan 10 10 9
238 IS 363 lr_morgan 1949 0.995 Morgan 10 10 9
239 IS 363 lr_morgan 1953 0.995 Morgan 10 10 9
240 IS 363 lr_morgan 1956 0.995 Morgan 10 10 9
241 IS 363 lr_morgan 1959 0.995 Morgan 10 10 9
242 IS 363 lr_morgan 1963 0.995 Morgan 10 10 9
243 IS 363 lr_morgan 1967 0.995 Morgan 10 10 9
244 IS 363 lr_morgan 1971 0.995 Morgan 10 10 9
245 IS 964 lr_morgan 1946 0.7299 Morgan 10 7 9
246 IS 964 lr_morgan 1949 0.7299 Morgan 10 7 9
247 IS 964 lr_morgan 1953 0.7299 Morgan 10 7 9
248 IS 964 lr_morgan 1956 0.7299 Morgan 10 7 9
249 IS 964 lr_morgan 1959 0.7299 Morgan 10 7 9
250 IS 964 lr_morgan 1963 0.7299 Morgan 10 7 9
251 IS 964 lr_morgan 1967 0.7299 Morgan 10 7 9
252 IS 964 lr_morgan 1971 0.7299 Morgan 10 7 9
253 IS 1249 lr_morgan 1946 0.005 Morgan 10 0 9
254 IS 1249 lr_morgan 1949 0.005 Morgan 10 0 9
255 IS 1249 lr_morgan 1953 0.005 Morgan 10 0 9
256 IS 1249 lr_morgan 1956 0.005 Morgan 10 0 9
257 IS 1249 lr_morgan 1959 0.005 Morgan 10 0 9
258 IS 1249 lr_morgan 1963 0.005 Morgan 10 0 9
259 IS 1249 lr_morgan 1967 0.005 Morgan 10 0 9
260 IS 1249 lr_morgan 1971 0.005 Morgan 10 0 9
261 IS 1325 lr_morgan 1946 0.3778 Morgan 10 4 9
262 IS 1325 lr_morgan 1949 0.3778 Morgan 10 4 9
263 IS 1325 lr_morgan 1953 0.3778 Morgan 10 4 9
264 IS 1325 lr_morgan 1956 0.3778 Morgan 10 4 9
265 IS 1325 lr_morgan 1959 0.3778 Morgan 10 4 9
266 IS 1325 lr_morgan 1963 0.3778 Morgan 10 4 9
267 IS 1325 lr_morgan 1967 0.3778 Morgan 10 4 9
268 IS 1325 lr_morgan 1971 0.3778 Morgan 10 4 9
269 IS 1517 lr_morgan 1967 0.11410000000000001 Morgan 10 1 9
270 IS 1517 lr_morgan 1971 0.11410000000000001 Morgan 10 1 9
271 IT 394 lr_morgan 1946 0.4218 Morgan 10 4 12
272 IT 394 lr_morgan 1948 0.4218 Morgan 10 4 12
273 IT 394 lr_morgan 1953 0.4218 Morgan 10 4 12
274 IT 394 lr_morgan 1958 0.4218 Morgan 10 4 12
275 IT 394 lr_morgan 1963 0.4218 Morgan 10 4 12
276 IT 394 lr_morgan 1968 0.4218 Morgan 10 4 12
277 IT 394 lr_morgan 1972 0.4218 Morgan 10 4 12
278 IT 742 lr_morgan 1946 0.2281 Morgan 10 2 12
279 IT 742 lr_morgan 1948 0.2281 Morgan 10 2 12
280 IT 742 lr_morgan 1953 0.2281 Morgan 10 2 12
281 IT 742 lr_morgan 1958 0.2281 Morgan 10 2 12
282 IT 742 lr_morgan 1963 0.2281 Morgan 10 2 12
283 IT 742 lr_morgan 1972 0.2281 Morgan 10 2 12
284 IT 934 lr_morgan 1946 0.5716 Morgan 10 6 12
285 IT 934 lr_morgan 1948 0.5716 Morgan 10 6 12
286 IT 934 lr_morgan 1953 0.5716 Morgan 10 6 12
287 IT 934 lr_morgan 1958 0.5716 Morgan 10 6 12
288 IT 934 lr_morgan 1963 0.5716 Morgan 10 6 12
289 IT 934 lr_morgan 1968 0.5716 Morgan 10 6 12
290 IT 934 lr_morgan 1972 0.5716 Morgan 10 6 12
291 IT 1126 lr_morgan 1948 0.36119999999999997 Morgan 10 4 12
292 IT 1126 lr_morgan 1953 0.36119999999999997 Morgan 10 4 12
293 IT 1126 lr_morgan 1958 0.36119999999999997 Morgan 10 4 12
294 IT 1126 lr_morgan 1963 0.36119999999999997 Morgan 10 4 12
295 IT 1126 lr_morgan 1972 0.36119999999999997 Morgan 10 4 12
296 IT 1461 lr_morgan 1946 0.7292000000000001 Morgan 10 7 12
297 IT 1461 lr_morgan 1948 0.7292000000000001 Morgan 10 7 12
298 IT 1461 lr_morgan 1953 0.7292000000000001 Morgan 10 7 12
299 IT 1461 lr_morgan 1958 0.7292000000000001 Morgan 10 7 12
300 IT 1461 lr_morgan 1963 0.7292000000000001 Morgan 10 7 12
301 IT 1461 lr_morgan 1968 0.7292000000000001 Morgan 10 7 12
302 IT 1461 lr_morgan 1972 0.7292000000000001 Morgan 10 7 12
303 LU 186 lr_morgan 1945 0.36219999999999997 Morgan 10 4 7
304 LU 186 lr_morgan 1948 0.36219999999999997 Morgan 10 4 7
305 LU 186 lr_morgan 1951 0.36219999999999997 Morgan 10 4 7
306 LU 186 lr_morgan 1954 0.36219999999999997 Morgan 10 4 7
307 LU 186 lr_morgan 1959 0.36219999999999997 Morgan 10 4 7
308 LU 186 lr_morgan 1964 0.36219999999999997 Morgan 10 4 7
309 LU 186 lr_morgan 1968 0.36219999999999997 Morgan 10 4 7
310 LU 300 lr_morgan 1945 0.9333 Morgan 10 9 5
311 LU 300 lr_morgan 1948 0.9333 Morgan 10 9 5
312 LU 300 lr_morgan 1951 0.9333 Morgan 10 9 5
313 LU 300 lr_morgan 1954 0.9333 Morgan 10 9 5
314 LU 300 lr_morgan 1959 0.9333 Morgan 10 9 5
315 LU 300 lr_morgan 1964 0.9333 Morgan 10 9 5
316 LU 300 lr_morgan 1968 0.9333 Morgan 10 9 5
317 LU 539 lr_morgan 1945 0.8576 Morgan 10 9 7
318 LU 539 lr_morgan 1948 0.8576 Morgan 10 9 7
319 LU 539 lr_morgan 1951 0.8576 Morgan 10 9 7
320 LU 539 lr_morgan 1954 0.8576 Morgan 10 9 7
321 LU 539 lr_morgan 1959 0.8576 Morgan 10 9 7
322 LU 539 lr_morgan 1964 0.8576 Morgan 10 9 7
323 LU 539 lr_morgan 1968 0.8576 Morgan 10 9 7
324 LU 1647 lr_morgan 1945 0.005 Morgan 10 0 7
325 LU 1647 lr_morgan 1948 0.005 Morgan 10 0 7
326 LU 1647 lr_morgan 1951 0.005 Morgan 10 0 7
327 LU 1647 lr_morgan 1954 0.005 Morgan 10 0 7
328 LU 1647 lr_morgan 1959 0.005 Morgan 10 0 7
329 LU 1647 lr_morgan 1964 0.005 Morgan 10 0 7
330 LU 1647 lr_morgan 1968 0.005 Morgan 10 0 7
331 NL 163 lr_morgan 1946 0.5556 Morgan 10 6 14
332 NL 163 lr_morgan 1948 0.5556 Morgan 10 6 14
333 NL 163 lr_morgan 1952 0.5556 Morgan 10 6 14
334 NL 163 lr_morgan 1956 0.5556 Morgan 10 6 14
335 NL 163 lr_morgan 1959 0.5556 Morgan 10 6 14
336 NL 163 lr_morgan 1963 0.5556 Morgan 10 6 14
337 NL 163 lr_morgan 1967 0.5556 Morgan 10 6 14
338 NL 163 lr_morgan 1971 0.5556 Morgan 10 6 14
339 NL 163 lr_morgan 1972 0.5556 Morgan 10 6 14
340 NL 459 lr_morgan 1946 0.005 Morgan 10 0 14
341 NL 459 lr_morgan 1948 0.005 Morgan 10 0 14
342 NL 828 lr_morgan 1946 0.7609999999999999 Morgan 10 8 14
343 NL 828 lr_morgan 1948 0.7609999999999999 Morgan 10 8 14
344 NL 828 lr_morgan 1952 0.7609999999999999 Morgan 10 8 14
345 NL 828 lr_morgan 1956 0.7609999999999999 Morgan 10 8 14
346 NL 828 lr_morgan 1959 0.7609999999999999 Morgan 10 8 14
347 NL 828 lr_morgan 1963 0.7609999999999999 Morgan 10 8 14
348 NL 828 lr_morgan 1967 0.7609999999999999 Morgan 10 8 14
349 NL 828 lr_morgan 1971 0.7609999999999999 Morgan 10 8 14
350 NL 828 lr_morgan 1972 0.7609999999999999 Morgan 10 8 14
351 NL 921 lr_morgan 1971 0.4604 Morgan 10 5 14
352 NL 921 lr_morgan 1972 0.4604 Morgan 10 5 14
353 NL 1102 lr_morgan 1946 0.6647 Morgan 10 7 14
354 NL 1102 lr_morgan 1948 0.6647 Morgan 10 7 14
355 NL 1102 lr_morgan 1952 0.6647 Morgan 10 7 14
356 NL 1102 lr_morgan 1956 0.6647 Morgan 10 7 14
357 NL 1102 lr_morgan 1959 0.6647 Morgan 10 7 14
358 NL 1102 lr_morgan 1963 0.6647 Morgan 10 7 14
359 NL 1102 lr_morgan 1967 0.6647 Morgan 10 7 14
360 NL 1102 lr_morgan 1971 0.6647 Morgan 10 7 14
361 NL 1102 lr_morgan 1972 0.6647 Morgan 10 7 14
362 NL 1178 lr_morgan 1946 0.9634999999999999 Morgan 10 10 13
363 NL 1178 lr_morgan 1948 0.9634999999999999 Morgan 10 10 13
364 NL 1234 lr_morgan 1946 0.2669 Morgan 10 3 14
365 NL 1234 lr_morgan 1948 0.2669 Morgan 10 3 14
366 NL 1234 lr_morgan 1952 0.2669 Morgan 10 3 14
367 NL 1234 lr_morgan 1956 0.2669 Morgan 10 3 14
368 NL 1234 lr_morgan 1959 0.2669 Morgan 10 3 14
369 NL 1234 lr_morgan 1963 0.2669 Morgan 10 3 14
370 NL 1234 lr_morgan 1967 0.2669 Morgan 10 3 14
371 NL 1234 lr_morgan 1971 0.2669 Morgan 10 3 14
372 NL 1234 lr_morgan 1972 0.2669 Morgan 10 3 14
373 NL 1390 lr_morgan 1946 0.5125 Morgan 10 5 14
374 NL 1390 lr_morgan 1948 0.5125 Morgan 10 5 14
375 NL 1390 lr_morgan 1952 0.5125 Morgan 10 5 14
376 NL 1390 lr_morgan 1956 0.5125 Morgan 10 5 14
377 NL 1390 lr_morgan 1959 0.5125 Morgan 10 5 14
378 NL 1390 lr_morgan 1963 0.5125 Morgan 10 5 14
379 NL 1390 lr_morgan 1967 0.5125 Morgan 10 5 14
380 NL 1390 lr_morgan 1971 0.5125 Morgan 10 5 14
381 NL 1390 lr_morgan 1972 0.5125 Morgan 10 5 14
382 NL 1581 lr_morgan 1971 0.1967 Morgan 10 2 11
383 NL 1581 lr_morgan 1972 0.1967 Morgan 10 2 11
384 NO 448 lr_morgan 1945 0.3529 Morgan 10 4 20
385 NO 448 lr_morgan 1949 0.3529 Morgan 10 4 20
386 NO 448 lr_morgan 1953 0.3529 Morgan 10 4 20
387 NO 448 lr_morgan 1957 0.3529 Morgan 10 4 20
388 NO 448 lr_morgan 1961 0.3529 Morgan 10 4 20
389 NO 448 lr_morgan 1965 0.3529 Morgan 10 4 20
390 NO 448 lr_morgan 1969 0.3529 Morgan 10 4 20
391 NO 448 lr_morgan 1973 0.3529 Morgan 10 4 20
392 NO 503 lr_morgan 1945 0.995 Morgan 10 10 20
393 NO 503 lr_morgan 1949 0.995 Morgan 10 10 20
394 NO 503 lr_morgan 1953 0.995 Morgan 10 10 20
395 NO 503 lr_morgan 1957 0.995 Morgan 10 10 20
396 NO 503 lr_morgan 1961 0.995 Morgan 10 10 20
397 NO 503 lr_morgan 1965 0.995 Morgan 10 10 20
398 NO 503 lr_morgan 1969 0.995 Morgan 10 10 20
399 NO 503 lr_morgan 1973 0.995 Morgan 10 10 20
400 NO 705 lr_morgan 1945 0.6907 Morgan 10 7 19
401 NO 705 lr_morgan 1949 0.6907 Morgan 10 7 19
402 NO 705 lr_morgan 1953 0.6907 Morgan 10 7 19
403 NO 705 lr_morgan 1957 0.6907 Morgan 10 7 19
404 NO 705 lr_morgan 1961 0.6907 Morgan 10 7 19
405 NO 705 lr_morgan 1965 0.6907 Morgan 10 7 19
406 NO 705 lr_morgan 1969 0.6907 Morgan 10 7 19
407 NO 705 lr_morgan 1973 0.6907 Morgan 10 7 19
408 NO 1072 lr_morgan 1945 0.7813 Morgan 10 8 20
409 NO 1072 lr_morgan 1949 0.7813 Morgan 10 8 20
410 NO 1072 lr_morgan 1953 0.7813 Morgan 10 8 20
411 NO 1072 lr_morgan 1957 0.7813 Morgan 10 8 20
412 NO 1072 lr_morgan 1961 0.7813 Morgan 10 8 20
413 NO 1072 lr_morgan 1965 0.7813 Morgan 10 8 20
414 NO 1072 lr_morgan 1969 0.7813 Morgan 10 8 20
415 NO 1072 lr_morgan 1973 0.7813 Morgan 10 8 20
416 NO 1079 lr_morgan 1945 0.005 Morgan 10 0 20
417 NO 1079 lr_morgan 1949 0.005 Morgan 10 0 20
418 NO 1079 lr_morgan 1953 0.005 Morgan 10 0 20
419 NO 1079 lr_morgan 1957 0.005 Morgan 10 0 20
420 NO 1173 lr_morgan 1945 0.6426999999999999 Morgan 10 6 20
421 NO 1173 lr_morgan 1949 0.6426999999999999 Morgan 10 6 20
422 NO 1173 lr_morgan 1953 0.6426999999999999 Morgan 10 6 20
423 NO 1173 lr_morgan 1957 0.6426999999999999 Morgan 10 6 20
424 NO 1173 lr_morgan 1961 0.6426999999999999 Morgan 10 6 20
425 NO 1173 lr_morgan 1965 0.6426999999999999 Morgan 10 6 20
426 NO 1173 lr_morgan 1969 0.6426999999999999 Morgan 10 6 20
427 NO 1173 lr_morgan 1973 0.6426999999999999 Morgan 10 6 20
428 SE 199 lr_morgan 1948 0.6456999999999999 Morgan 10 6 19
429 SE 199 lr_morgan 1952 0.6456999999999999 Morgan 10 6 19
430 SE 199 lr_morgan 1956 0.6456999999999999 Morgan 10 6 19
431 SE 199 lr_morgan 1958 0.6456999999999999 Morgan 10 6 19
432 SE 199 lr_morgan 1960 0.6456999999999999 Morgan 10 6 19
433 SE 199 lr_morgan 1964 0.6456999999999999 Morgan 10 6 19
434 SE 199 lr_morgan 1968 0.6456999999999999 Morgan 10 6 19
435 SE 199 lr_morgan 1970 0.6456999999999999 Morgan 10 6 19
436 SE 199 lr_morgan 1973 0.6456999999999999 Morgan 10 6 19
437 SE 487 lr_morgan 1948 0.3251 Morgan 10 3 20
438 SE 487 lr_morgan 1952 0.3251 Morgan 10 3 20
439 SE 487 lr_morgan 1956 0.3251 Morgan 10 3 20
440 SE 487 lr_morgan 1958 0.3251 Morgan 10 3 20
441 SE 487 lr_morgan 1960 0.3251 Morgan 10 3 20
442 SE 487 lr_morgan 1964 0.3251 Morgan 10 3 20
443 SE 487 lr_morgan 1968 0.3251 Morgan 10 3 20
444 SE 487 lr_morgan 1970 0.3251 Morgan 10 3 20
445 SE 487 lr_morgan 1973 0.3251 Morgan 10 3 20
446 SE 690 lr_morgan 1948 0.995 Morgan 10 10 20
447 SE 690 lr_morgan 1952 0.995 Morgan 10 10 20
448 SE 690 lr_morgan 1956 0.995 Morgan 10 10 20
449 SE 690 lr_morgan 1958 0.995 Morgan 10 10 20
450 SE 690 lr_morgan 1960 0.995 Morgan 10 10 20
451 SE 690 lr_morgan 1964 0.995 Morgan 10 10 20
452 SE 690 lr_morgan 1968 0.995 Morgan 10 10 20
453 SE 690 lr_morgan 1970 0.995 Morgan 10 10 20
454 SE 690 lr_morgan 1973 0.995 Morgan 10 10 20
455 SE 830 lr_morgan 1948 0.005 Morgan 10 0 20
456 SE 830 lr_morgan 1952 0.005 Morgan 10 0 20
457 SE 830 lr_morgan 1956 0.005 Morgan 10 0 20
458 SE 830 lr_morgan 1958 0.005 Morgan 10 0 20
459 SE 830 lr_morgan 1960 0.005 Morgan 10 0 20
460 SE 830 lr_morgan 1964 0.005 Morgan 10 0 20
461 SE 830 lr_morgan 1968 0.005 Morgan 10 0 20
462 SE 830 lr_morgan 1970 0.005 Morgan 10 0 20
463 SE 830 lr_morgan 1973 0.005 Morgan 10 0 20
464 SE 1274 lr_morgan 1948 0.7444 Morgan 10 7 20
465 SE 1274 lr_morgan 1952 0.7444 Morgan 10 7 20
466 SE 1274 lr_morgan 1956 0.7444 Morgan 10 7 20
467 SE 1274 lr_morgan 1958 0.7444 Morgan 10 7 20
468 SE 1274 lr_morgan 1960 0.7444 Morgan 10 7 20
469 SE 1274 lr_morgan 1964 0.7444 Morgan 10 7 20
470 SE 1274 lr_morgan 1968 0.7444 Morgan 10 7 20
471 SE 1274 lr_morgan 1970 0.7444 Morgan 10 7 20
472 SE 1274 lr_morgan 1973 0.7444 Morgan 10 7 20
+161
View File
@@ -0,0 +1,161 @@
country,period,party_abbrev,party_name,position,n_surveys,sd
DNK,1919-1939,SOCd,Social Democrats,0.0,19,0.0
DNK,1919-1939,RAD,Radicals,33.94,19,4.62
DNK,1919-1939,LIB,Liberals,71.87,19,7.99
DNK,1919-1939,CONS,Conservatives,100.0,19,0.0
DNK,1945-1973,COMM,Communists,0.0,22,0.0
DNK,1945-1973,LS,Left Socialists,6.5,22,4.17
DNK,1945-1973,SOCL,Socialists (SF),19.54,22,5.93
DNK,1945-1973,SOCd,Social Democrats,41.49,22,7.19
DNK,1945-1973,RAD,Radicals,61.85,22,6.23
DNK,1945-1973,LC,Liberal Center,67.9,18,8.27
DNK,1945-1973,LIB,Liberals,81.98,22,6.46
DNK,1945-1973,JUST,Justice Party,84.08,17,13.38
DNK,1945-1973,CONS,Conservatives,100.0,22,0.0
FIN,1919-1939,SKDL,Communist,0.0,12,0.0
FIN,1919-1939,SOCd,Social Democrats,24.92,12,2.8
FIN,1919-1939,PROG,National Progressives,57.5,12,4.68
FIN,1919-1939,AGR,Agrarian Union,59.32,12,4.46
FIN,1919-1939,SWPP,Swedish People's Party,67.79,12,6.57
FIN,1919-1939,CONS,Finnish Conservative Party,91.96,12,3.9
FIN,1919-1939,NPF,Finnish Patriotic Movement,100.0,12,0.0
FIN,1945-1973,PDEM,Democratic League,0.0,15,0.0
FIN,1945-1973,SDWS,Workers Smallholders SD League,21.15,15,6.73
FIN,1945-1973,SOCd,Social Democrats,25.78,15,3.41
FIN,1945-1973,CENT,Center Party,57.76,13,4.39
FIN,1945-1973,FRP,Finnish Rural Party,64.11,13,16.91
FIN,1945-1973,LIB,Finnish People Party/Liberal League,70.34,15,5.5
FIN,1945-1973,SWPP,Swedish People's Party,79.75,15,6.23
FIN,1945-1973,CONS,Finnish Conservative Party,98.55,15,2.89
ISL,1919-1939,COMM,Communist People Alliance,0.0,6,0.0
ISL,1919-1939,SOCd,Social Democratic Party,21.01,6,4.25
ISL,1919-1939,PROG,Progressives/Farmers Party,62.11,6,12.23
ISL,1919-1939,LIB,Liberal Party,63.02,6,10.81
ISL,1919-1939,INDP,Independence Party,81.28,6,5.23
ISL,1919-1939,CONS,Conservative Party,100.0,6,0.0
ISL,1945-1973,COMM,Communist People Alliance,0.0,9,0.0
ISL,1945-1973,LLIB,Liberal Left,11.41,9,3.89
ISL,1945-1973,SOCd,Social Democratic Party,37.78,9,8.8
ISL,1945-1973,PROG,Progressive Party,72.99,9,7.7
ISL,1945-1973,INDP,Independence Party,100.0,9,0.0
NOR,1919-1939,LAB,Labor Party,0.0,19,0.0
NOR,1919-1939,LIB,Liberal Party,57.71,19,7.47
NOR,1919-1939,AGR,Agrarian Party,70.61,19,8.64
NOR,1919-1939,CONS,Conservative Party,100.0,19,0.0
NOR,1945-1973,COMM,Communist Party,0.0,20,0.0
NOR,1945-1973,SOCL,Socialist People Party,7.45,20,4.16
NOR,1945-1973,LAB,Labor Party,35.29,20,5.05
NOR,1945-1973,LIB,Liberal Party,64.27,20,6.84
NOR,1945-1973,CHPP,Christian People Party,69.07,19,8.84
NOR,1945-1973,CENT,Center Party,78.13,20,0.0
NOR,1945-1973,CONS,Conservative Party,100.0,20,0.0
SWE,1919-1939,COMM,Communist Party,0.0,15,0.0
SWE,1919-1939,SOCd,Social Democratic Party,25.85,15,5.38
SWE,1919-1939,AGR,Agrarian Party,58.57,15,5.43
SWE,1919-1939,LIB,Liberal Party,74.19,15,4.41
SWE,1919-1939,CONS,Conservative Party,100.0,15,0.0
SWE,1945-1973,COMM,Communist Party,0.0,20,0.0
SWE,1945-1973,SOCd,Social Democratic Party,32.51,20,6.17
SWE,1945-1973,CENT,Center/Agrarian Party,64.57,19,3.34
SWE,1945-1973,LIB,Liberal/People Party,74.44,20,6.2
SWE,1945-1973,CONS,Conservative Party,100.0,20,0.0
NLD,1919-1939,CPN,Communist Party,0.0,6,0.0
NLD,1919-1939,SOCd,Social Democratic Party (SDAP),18.32,6,5.41
NLD,1919-1939,RAD,Radical Party,47.04,6,3.14
NLD,1919-1939,KVP,Catholic Party,56.05,6,6.44
NLD,1919-1939,CHU,Christian Historical Union,69.2,6,7.59
NLD,1919-1939,ULIB,United Liberal Party,69.7,6,3.13
NLD,1919-1939,FLIB,Free Liberal Party,70.83,6,6.14
NLD,1919-1939,LIB,Liberal Party,71.94,6,4.1
NLD,1919-1939,ARP,Anti-Revolutionary Party,72.43,6,9.1
NLD,1919-1939,ECUM,Ecumenical Party,76.25,2,19.45
NLD,1919-1939,SGP,Political Reformed Party,88.5,6,1.32
NLD,1919-1939,NSB,National Social Movement,100.0,6,0.0
NLD,1945-1973,CPN,Communist Party,0.0,14,0.0
NLD,1945-1973,PSP,Pacifist Socialist Party,7.66,14,4.65
NLD,1945-1973,PPR,Political Radicals,19.67,11,4.98
NLD,1945-1973,PVDA,Labor Party,26.69,14,4.91
NLD,1945-1973,D66,Radical Democrats,39.11,13,4.06
NLD,1945-1973,DS70,Social Democratic splinter,46.04,14,6.15
NLD,1945-1973,KVP,Catholic Party,51.25,14,2.08
NLD,1945-1973,ARP,Anti-Revolutionary Party,55.56,14,8.58
NLD,1945-1973,CHU,Christian Historical Union,66.47,14,5.65
NLD,1945-1973,VVD,Liberal Party,76.1,14,5.82
NLD,1945-1973,SGP,Political Reform Party,96.35,13,3.84
NLD,1945-1973,GPV,Reformed Political Association,96.7,12,3.88
NLD,1945-1973,BP,Peasant Party (Poujadist),96.82,11,4.46
BEL,1919-1939,COMM,Communist Party,0.0,11,0.0
BEL,1919-1939,POB,Social Democratic Party,25.11,11,5.48
BEL,1919-1939,CATH,Catholic Party,67.85,11,9.78
BEL,1919-1939,LIB,Liberal Party,78.84,10,8.27
BEL,1919-1939,FNAT,Flemish Nationalist parties,80.78,11,11.93
BEL,1919-1939,REX,Rexist,98.72,10,3.14
BEL,1945-1973,COMM,Communist Party,0.0,17,0.0
BEL,1945-1973,PSB,Socialist Party,31.93,17,5.25
BEL,1945-1973,RW,Rassemblement Wallon,53.65,16,9.73
BEL,1945-1973,PSC,Christian Social Party,70.9,17,6.59
BEL,1945-1973,FDF,Front Democratique Wallon,71.89,17,13.85
BEL,1945-1973,VOLK,Volksunie,83.21,17,6.64
BEL,1945-1973,PLP,Liberal Party,100.0,17,0.0
FRA,1946-1958,PCF,Communist Party,0.0,23,0.0
FRA,1946-1958,SFIO,Socialist Party,23.65,23,4.61
FRA,1946-1958,MRP,Popular Republican Movement,43.12,23,6.73
FRA,1946-1958,RDA,Radical Party,43.37,20,6.73
FRA,1946-1958,UDSR,Democratic Socialist Union,44.62,20,6.68
FRA,1946-1958,RAD,Radical Democratic Assembly,50.94,22,5.3
FRA,1946-1958,RS,Social Republicans,66.52,23,9.15
FRA,1946-1958,RPF,Rally of the French People,70.0,23,7.25
FRA,1946-1958,AR,Republican Action,72.93,20,3.74
FRA,1946-1958,ARS,Republican Social Action,77.38,16,6.83
FRA,1946-1958,RI,Independent Republicans,82.62,19,8.05
FRA,1946-1958,CNIP,National Center of Independents,83.5,19,5.1
FRA,1946-1958,PUS,Pro-Gaullist faction,85.0,10,5.14
FRA,1946-1958,PAYS,Peasant Party,85.23,14,6.55
FRA,1946-1958,AP,Popular Action,85.4,11,4.34
FRA,1946-1958,PRL,Republican Liberty Party,87.56,17,5.66
FRA,1946-1958,POUJ,Poujadists,100.0,22,0.0
DEU,1919-1930,KPD,Communist Party,0.0,15,0.0
DEU,1919-1930,SDAP,Social Democratic Party,25.58,15,4.62
DEU,1919-1930,DDP,Democratic Party,45.57,15,4.79
DEU,1919-1930,DZP,Center Party,51.25,15,6.35
DEU,1919-1930,BVP,Bavarian People's Party,63.17,11,5.63
DEU,1919-1930,DVP,German People's Party,65.17,15,4.79
DEU,1919-1930,RDMW,Economic Party,70.64,11,5.93
DEU,1919-1930,LVP,Rural People's Party,80.35,9,6.53
DEU,1919-1930,DNVP,German National People's Party,88.17,15,3.68
DEU,1919-1930,NAZI,National Socialist Party,100.0,15,0.0
ITA,1946-1975,PCI,Communist Party,3.08,12,3.37
ITA,1946-1975,PSIU,Socialist Party of Proletarian Unity,3.61,12,4.0
ITA,1946-1975,PSI,Socialist Party (Nenni),22.81,12,3.11
ITA,1946-1975,PSDI,Social Democratic Party,36.12,12,3.77
ITA,1946-1975,PRI,Republican Party,42.18,12,2.37
ITA,1946-1975,DC,Christian Democratic Party,57.16,12,5.44
ITA,1946-1975,PLI,Liberal Party,72.92,12,3.02
ITA,1946-1975,MON,Monarchists,91.68,12,2.41
ITA,1946-1975,MSI,Italian Social Movement,100.0,12,0.0
LUX,1945-1973,COMM,Communist Party,0.0,7,0.0
LUX,1945-1973,SOCd,Socialist Party,36.22,7,12.83
LUX,1945-1973,CSOC,Christian Social Party,85.76,7,13.47
LUX,1945-1973,GRPD,Democratic Group,93.33,5,11.55
ISR,1949-1973,RAKA,Rakah,0.0,10,0.0
ISR,1949-1973,MAKI,Maki,3.46,10,2.79
ISR,1949-1973,MAPM,Mapam,18.04,10,5.85
ISR,1949-1973,MADT,Mapam Ahdut Haavoda,26.78,10,9.24
ISR,1949-1973,ADUT,Ahdut Haavoda,34.92,10,12.63
ISR,1949-1973,MAAR,Maarach,46.79,10,8.39
ISR,1949-1973,LAB,Labor Alignment,48.08,10,6.65
ISR,1949-1973,MAPI,Mapai,48.27,10,6.99
ISR,1949-1973,PAUG,Poalei Agudat,55.82,6,5.24
ISR,1949-1973,RAFI,Rafi,60.42,7,6.71
ISR,1949-1973,PROG,Progressives,65.0,8,5.38
ISR,1949-1973,ILIB,Independent Liberals,65.8,9,7.55
ISR,1949-1973,NRP,National Religious Party,69.0,7,8.21
ISR,1949-1973,URF,United Religious Front,77.0,5,10.63
ISR,1949-1973,LIB,Liberals,78.57,9,10.35
ISR,1949-1973,NATL,National List,79.67,6,10.87
ISR,1949-1973,TORA,Torah,85.29,6,0.67
ISR,1949-1973,LIKD,Likud,87.39,6,11.04
ISR,1949-1973,ZION,General Zionists,87.95,9,3.35
ISR,1949-1973,GHAL,Gahal,90.69,8,4.71
ISR,1949-1973,AGDT,Agudat Israel,96.25,5,2.75
ISR,1949-1973,HRUT,Herut,98.0,9,2.71
1 country period party_abbrev party_name position n_surveys sd
2 DNK 1919-1939 SOCd Social Democrats 0.0 19 0.0
3 DNK 1919-1939 RAD Radicals 33.94 19 4.62
4 DNK 1919-1939 LIB Liberals 71.87 19 7.99
5 DNK 1919-1939 CONS Conservatives 100.0 19 0.0
6 DNK 1945-1973 COMM Communists 0.0 22 0.0
7 DNK 1945-1973 LS Left Socialists 6.5 22 4.17
8 DNK 1945-1973 SOCL Socialists (SF) 19.54 22 5.93
9 DNK 1945-1973 SOCd Social Democrats 41.49 22 7.19
10 DNK 1945-1973 RAD Radicals 61.85 22 6.23
11 DNK 1945-1973 LC Liberal Center 67.9 18 8.27
12 DNK 1945-1973 LIB Liberals 81.98 22 6.46
13 DNK 1945-1973 JUST Justice Party 84.08 17 13.38
14 DNK 1945-1973 CONS Conservatives 100.0 22 0.0
15 FIN 1919-1939 SKDL Communist 0.0 12 0.0
16 FIN 1919-1939 SOCd Social Democrats 24.92 12 2.8
17 FIN 1919-1939 PROG National Progressives 57.5 12 4.68
18 FIN 1919-1939 AGR Agrarian Union 59.32 12 4.46
19 FIN 1919-1939 SWPP Swedish People's Party 67.79 12 6.57
20 FIN 1919-1939 CONS Finnish Conservative Party 91.96 12 3.9
21 FIN 1919-1939 NPF Finnish Patriotic Movement 100.0 12 0.0
22 FIN 1945-1973 PDEM Democratic League 0.0 15 0.0
23 FIN 1945-1973 SDWS Workers Smallholders SD League 21.15 15 6.73
24 FIN 1945-1973 SOCd Social Democrats 25.78 15 3.41
25 FIN 1945-1973 CENT Center Party 57.76 13 4.39
26 FIN 1945-1973 FRP Finnish Rural Party 64.11 13 16.91
27 FIN 1945-1973 LIB Finnish People Party/Liberal League 70.34 15 5.5
28 FIN 1945-1973 SWPP Swedish People's Party 79.75 15 6.23
29 FIN 1945-1973 CONS Finnish Conservative Party 98.55 15 2.89
30 ISL 1919-1939 COMM Communist People Alliance 0.0 6 0.0
31 ISL 1919-1939 SOCd Social Democratic Party 21.01 6 4.25
32 ISL 1919-1939 PROG Progressives/Farmers Party 62.11 6 12.23
33 ISL 1919-1939 LIB Liberal Party 63.02 6 10.81
34 ISL 1919-1939 INDP Independence Party 81.28 6 5.23
35 ISL 1919-1939 CONS Conservative Party 100.0 6 0.0
36 ISL 1945-1973 COMM Communist People Alliance 0.0 9 0.0
37 ISL 1945-1973 LLIB Liberal Left 11.41 9 3.89
38 ISL 1945-1973 SOCd Social Democratic Party 37.78 9 8.8
39 ISL 1945-1973 PROG Progressive Party 72.99 9 7.7
40 ISL 1945-1973 INDP Independence Party 100.0 9 0.0
41 NOR 1919-1939 LAB Labor Party 0.0 19 0.0
42 NOR 1919-1939 LIB Liberal Party 57.71 19 7.47
43 NOR 1919-1939 AGR Agrarian Party 70.61 19 8.64
44 NOR 1919-1939 CONS Conservative Party 100.0 19 0.0
45 NOR 1945-1973 COMM Communist Party 0.0 20 0.0
46 NOR 1945-1973 SOCL Socialist People Party 7.45 20 4.16
47 NOR 1945-1973 LAB Labor Party 35.29 20 5.05
48 NOR 1945-1973 LIB Liberal Party 64.27 20 6.84
49 NOR 1945-1973 CHPP Christian People Party 69.07 19 8.84
50 NOR 1945-1973 CENT Center Party 78.13 20 0.0
51 NOR 1945-1973 CONS Conservative Party 100.0 20 0.0
52 SWE 1919-1939 COMM Communist Party 0.0 15 0.0
53 SWE 1919-1939 SOCd Social Democratic Party 25.85 15 5.38
54 SWE 1919-1939 AGR Agrarian Party 58.57 15 5.43
55 SWE 1919-1939 LIB Liberal Party 74.19 15 4.41
56 SWE 1919-1939 CONS Conservative Party 100.0 15 0.0
57 SWE 1945-1973 COMM Communist Party 0.0 20 0.0
58 SWE 1945-1973 SOCd Social Democratic Party 32.51 20 6.17
59 SWE 1945-1973 CENT Center/Agrarian Party 64.57 19 3.34
60 SWE 1945-1973 LIB Liberal/People Party 74.44 20 6.2
61 SWE 1945-1973 CONS Conservative Party 100.0 20 0.0
62 NLD 1919-1939 CPN Communist Party 0.0 6 0.0
63 NLD 1919-1939 SOCd Social Democratic Party (SDAP) 18.32 6 5.41
64 NLD 1919-1939 RAD Radical Party 47.04 6 3.14
65 NLD 1919-1939 KVP Catholic Party 56.05 6 6.44
66 NLD 1919-1939 CHU Christian Historical Union 69.2 6 7.59
67 NLD 1919-1939 ULIB United Liberal Party 69.7 6 3.13
68 NLD 1919-1939 FLIB Free Liberal Party 70.83 6 6.14
69 NLD 1919-1939 LIB Liberal Party 71.94 6 4.1
70 NLD 1919-1939 ARP Anti-Revolutionary Party 72.43 6 9.1
71 NLD 1919-1939 ECUM Ecumenical Party 76.25 2 19.45
72 NLD 1919-1939 SGP Political Reformed Party 88.5 6 1.32
73 NLD 1919-1939 NSB National Social Movement 100.0 6 0.0
74 NLD 1945-1973 CPN Communist Party 0.0 14 0.0
75 NLD 1945-1973 PSP Pacifist Socialist Party 7.66 14 4.65
76 NLD 1945-1973 PPR Political Radicals 19.67 11 4.98
77 NLD 1945-1973 PVDA Labor Party 26.69 14 4.91
78 NLD 1945-1973 D66 Radical Democrats 39.11 13 4.06
79 NLD 1945-1973 DS70 Social Democratic splinter 46.04 14 6.15
80 NLD 1945-1973 KVP Catholic Party 51.25 14 2.08
81 NLD 1945-1973 ARP Anti-Revolutionary Party 55.56 14 8.58
82 NLD 1945-1973 CHU Christian Historical Union 66.47 14 5.65
83 NLD 1945-1973 VVD Liberal Party 76.1 14 5.82
84 NLD 1945-1973 SGP Political Reform Party 96.35 13 3.84
85 NLD 1945-1973 GPV Reformed Political Association 96.7 12 3.88
86 NLD 1945-1973 BP Peasant Party (Poujadist) 96.82 11 4.46
87 BEL 1919-1939 COMM Communist Party 0.0 11 0.0
88 BEL 1919-1939 POB Social Democratic Party 25.11 11 5.48
89 BEL 1919-1939 CATH Catholic Party 67.85 11 9.78
90 BEL 1919-1939 LIB Liberal Party 78.84 10 8.27
91 BEL 1919-1939 FNAT Flemish Nationalist parties 80.78 11 11.93
92 BEL 1919-1939 REX Rexist 98.72 10 3.14
93 BEL 1945-1973 COMM Communist Party 0.0 17 0.0
94 BEL 1945-1973 PSB Socialist Party 31.93 17 5.25
95 BEL 1945-1973 RW Rassemblement Wallon 53.65 16 9.73
96 BEL 1945-1973 PSC Christian Social Party 70.9 17 6.59
97 BEL 1945-1973 FDF Front Democratique Wallon 71.89 17 13.85
98 BEL 1945-1973 VOLK Volksunie 83.21 17 6.64
99 BEL 1945-1973 PLP Liberal Party 100.0 17 0.0
100 FRA 1946-1958 PCF Communist Party 0.0 23 0.0
101 FRA 1946-1958 SFIO Socialist Party 23.65 23 4.61
102 FRA 1946-1958 MRP Popular Republican Movement 43.12 23 6.73
103 FRA 1946-1958 RDA Radical Party 43.37 20 6.73
104 FRA 1946-1958 UDSR Democratic Socialist Union 44.62 20 6.68
105 FRA 1946-1958 RAD Radical Democratic Assembly 50.94 22 5.3
106 FRA 1946-1958 RS Social Republicans 66.52 23 9.15
107 FRA 1946-1958 RPF Rally of the French People 70.0 23 7.25
108 FRA 1946-1958 AR Republican Action 72.93 20 3.74
109 FRA 1946-1958 ARS Republican Social Action 77.38 16 6.83
110 FRA 1946-1958 RI Independent Republicans 82.62 19 8.05
111 FRA 1946-1958 CNIP National Center of Independents 83.5 19 5.1
112 FRA 1946-1958 PUS Pro-Gaullist faction 85.0 10 5.14
113 FRA 1946-1958 PAYS Peasant Party 85.23 14 6.55
114 FRA 1946-1958 AP Popular Action 85.4 11 4.34
115 FRA 1946-1958 PRL Republican Liberty Party 87.56 17 5.66
116 FRA 1946-1958 POUJ Poujadists 100.0 22 0.0
117 DEU 1919-1930 KPD Communist Party 0.0 15 0.0
118 DEU 1919-1930 SDAP Social Democratic Party 25.58 15 4.62
119 DEU 1919-1930 DDP Democratic Party 45.57 15 4.79
120 DEU 1919-1930 DZP Center Party 51.25 15 6.35
121 DEU 1919-1930 BVP Bavarian People's Party 63.17 11 5.63
122 DEU 1919-1930 DVP German People's Party 65.17 15 4.79
123 DEU 1919-1930 RDMW Economic Party 70.64 11 5.93
124 DEU 1919-1930 LVP Rural People's Party 80.35 9 6.53
125 DEU 1919-1930 DNVP German National People's Party 88.17 15 3.68
126 DEU 1919-1930 NAZI National Socialist Party 100.0 15 0.0
127 ITA 1946-1975 PCI Communist Party 3.08 12 3.37
128 ITA 1946-1975 PSIU Socialist Party of Proletarian Unity 3.61 12 4.0
129 ITA 1946-1975 PSI Socialist Party (Nenni) 22.81 12 3.11
130 ITA 1946-1975 PSDI Social Democratic Party 36.12 12 3.77
131 ITA 1946-1975 PRI Republican Party 42.18 12 2.37
132 ITA 1946-1975 DC Christian Democratic Party 57.16 12 5.44
133 ITA 1946-1975 PLI Liberal Party 72.92 12 3.02
134 ITA 1946-1975 MON Monarchists 91.68 12 2.41
135 ITA 1946-1975 MSI Italian Social Movement 100.0 12 0.0
136 LUX 1945-1973 COMM Communist Party 0.0 7 0.0
137 LUX 1945-1973 SOCd Socialist Party 36.22 7 12.83
138 LUX 1945-1973 CSOC Christian Social Party 85.76 7 13.47
139 LUX 1945-1973 GRPD Democratic Group 93.33 5 11.55
140 ISR 1949-1973 RAKA Rakah 0.0 10 0.0
141 ISR 1949-1973 MAKI Maki 3.46 10 2.79
142 ISR 1949-1973 MAPM Mapam 18.04 10 5.85
143 ISR 1949-1973 MADT Mapam Ahdut Haavoda 26.78 10 9.24
144 ISR 1949-1973 ADUT Ahdut Haavoda 34.92 10 12.63
145 ISR 1949-1973 MAAR Maarach 46.79 10 8.39
146 ISR 1949-1973 LAB Labor Alignment 48.08 10 6.65
147 ISR 1949-1973 MAPI Mapai 48.27 10 6.99
148 ISR 1949-1973 PAUG Poalei Agudat 55.82 6 5.24
149 ISR 1949-1973 RAFI Rafi 60.42 7 6.71
150 ISR 1949-1973 PROG Progressives 65.0 8 5.38
151 ISR 1949-1973 ILIB Independent Liberals 65.8 9 7.55
152 ISR 1949-1973 NRP National Religious Party 69.0 7 8.21
153 ISR 1949-1973 URF United Religious Front 77.0 5 10.63
154 ISR 1949-1973 LIB Liberals 78.57 9 10.35
155 ISR 1949-1973 NATL National List 79.67 6 10.87
156 ISR 1949-1973 TORA Torah 85.29 6 0.67
157 ISR 1949-1973 LIKD Likud 87.39 6 11.04
158 ISR 1949-1973 ZION General Zionists 87.95 9 3.35
159 ISR 1949-1973 GHAL Gahal 90.69 8 4.71
160 ISR 1949-1973 AGDT Agudat Israel 96.25 5 2.75
161 ISR 1949-1973 HRUT Herut 98.0 9 2.71
File diff suppressed because it is too large Load Diff
+732
View File
@@ -0,0 +1,732 @@
partyfacts_id,party_name_english,party_name_short
3,Social Democratic Party,SDP
4,We Ourselves,SF
6,Liberal-Socialists of Chile,UCC
10,The Greens,B90/Gruene
14,Radical Party,PR
18,Estonian United Peoples Party,EÜRP
29,Social Democratic Party of Switzerland,SPS/PSS
34,Communist Party,PCI
42,Movement for a Better Hungary,Jobbik
43,List Dedecker,LDD
45,Democrats 66,D66
48,Communist Party of Greece,KKE
49,Open Flemish Liberals and Democrats,openVLD
50,South Schleswig Voters Union,SSW
53,Christian Democrats,K
54,Party for Democracy,PPD
57,Democratic Left Alliance,SLD
59,Democratic Change,CD
63,Christian Democratic Movement,KDH
64,Lithuanian Social Democratic Party,LSDP
72,Croatian Peasant Party,HSS
78,Democratic Centre,DC
81,Canarian Coalition-New Canaries,CCa-PNC-NC
82,Union of the Democratic Centre,EDIK
86,The Left. Party of Democratic Socialism,L-PDS
96,Slovenian National Party,SNS
97,People-Animals-Nature,PAN
101,Progress Party,FrP
102,Labour Party,DP
109,Worker's Party of the Land of Israel,Mapai
110,Estonian People's Union,ERL
118,National Party,PN
120,Social Democratic Party,PSD
150,Progressive Party,Progressive
152,New Democratic Party,NDP
155,People's Democratic Party,PDP
162,Communist Party of Chile,PC
163,Anti-Revolutionary Party,ARP
169,United Workers' Party,MAPAM
171,Liberal Democratic Party,LDP
172,Reformatory Political Federation,RPF
176,For Human Rights in a United Latvia,PCTVL
177,Czech Social Democratic Party,ČSSD
190,Democratic Party of Albania,PD
193,Homeland Union,TS
194,Social Christian Reformist Party,PRSC
199,Centre Party,CP
208,Workers' Party,WP
209,Humanist Party,PH
212,Liberal Alliance,NY
214,Croatian Social-Liberal Party,HSLS
216,Democratic Revolutionary Party,PRD
223,"Tradition, Responsibility, Prosperity 09",TOP09
225,Party of Brazilian Social Democracy,PSDB
232,Progressive Conservative Party,PCP
235,Israel is Our Home,YB
237,Order and Justice,PTT
246,Patriotic Society Party,PSP
247,United Left,IU
249,Liberal People's Party,LKP
253,Croatian Party of Rights,HSP
257,National Restoration Party,PRN
258,Bridge,Most-Hid
259,Italian Renewal,RI
263,Bulgarian People's Union,BNS
271,The Left,DL
275,Party of Liberty and Progress,PVV/PLP
276,Social Democratic Party,PSD
277,Danish Communist Party,DKP
279,Daisy - Democracy is Freedom,M-DL
281,Walloon Rally,RW
285,Liberal National Party of Queensland,LNP
292,Alliance of Independent Social Democrats,SNSD
298,Party of Freedom,PVV
300,Democratic Party,DP/PD
304,Christian Democratic Union,KDS
306,Justice and Development Party,AKP
308,Swiss People's Party,SVP/UDC
310,Portuguese Communist Party,PCP
311,Direction-Social Democracy,Smer
312,Political Spring,Pola
329,Socialist People's Party,SF
335,Democratic Unionist Party,DUP
338,Nationalist Party of Australia,NAT
340,Communist Party of Slovakia,KSS
349,Party of the Hungarian Coalition,SMK/MKP
351,True Path Party,DYP
352,People's New Party,PNP
356,Workers' Party,PT
357,Party for Bosnia and Herzegovina,SBiH
362,Colombian Liberal Party,PLC
363,Independence Party,Sj
378,Ecologist Greens,OP
379,Social Democratic Party,SD
382,Liberal Party,PL
383,Social Democratic Party of Germany,SPD
390,Christian Democratic Party,PDC
394,Italian Republican Party,PRI
405,Francophone Christian Social Party and Flemish Christian People's Party,PSC/CVP
409,Sweden Democrats,SD
421,Democratic Front for Peace and Equality,HADASH
424,Australian Labor Party,ALP
431,Democratic Party of Albanians,DPA/PDSh
432,Democratic Party,Democrats
433,National Front,FN
434,Progressive and Independent Liberal Party,ILP
437,Social Democrat Radical Party,PRSD
441,People's Party,PP
442,Movement towards Socialism - Political Instrument for the Sovereignty of the Peoples,MAS-IPSP
444,Liberal Party of Switzerland,LPS/PLS
446,Mexican Green Ecologist Party,PVEM
447,United Arab List,Ra'am
448,Labour Party,Ap
455,Sephardi Torah Guardians,Shas
456,Green Ecology Party,MP
457,Left Green Movement,VGF
458,Brazilian Labour Party,PTB
459,Communist Party of the Netherlands,CPN
463,Austrian Freedom Party,FPÖ
466,Civic Democratic Party,ODS
467,Democratic Party of Pensioners of Slovenia,DeSUS
469,Democratic Coalition,DK
472,Slovenian Democratic Party,SDS
474,For Real,Zares
479,Green Union,VL
480,Belgian Socialist Party,BSP/PSB
481,National Liberal Party,PNL
484,Forward,Kadima
487,Social Democratic Labour Party,SAP
491,Res Publica Party,ERP
492,Social Democratic Federation,SDF
495,National Coalition,KK
496,Democratic Movement,MoDem
500,Francophone Socialist Party,PS
503,Conservative Party,H
506,National Alliance 'All For Latvia! - For Fatherland and Freedom/LNNK',NA
509,Rally for the Republic,RPR
513,Istrian Democratic Assembly,IDS
514,Popular Action,AP
516,Progressive Left Coalition,SYN
525,Women's Alliance,Kv
528,Green!,Groen!
529,Peruvian Aprista Party,PAP
533,Estonian Center Party,K
536,Conservative People's Party,KF
537,Social Democratic Party of Bosnia and Herzegovina,SDP BiH
539,Christian Social People's Party,CSV/PCS
540,Liberal Party,Liberal
546,Lithuanian Democratic Labour Party,LDDP
553,Flemish Bloc,VB
554,Liberal Reformation Party,PRL
556,Election Action of Lithuania's Poles,LLRA/AWPL
557,Change,SHINUI
560,Movement for a Democratic Slovakia,HZDS
561,Party of Radical Change,PCR
562,Labour Party,Labour
563,Democratic Coalition,DISY
573,Free Democratic Party,FDP
575,Union for Peru,UPP
591,New Zealand First Party,NZF
594,Democratic Alliance Party,AD
598,Proletarian Unity Party for Communism The Manifesto + Proletarian Unity Party,PdUP
599,Alliance for the Future of Austria,BZÖ
601,United Kingdom Independence Party,UKIP
602,Polish Peasants' Party,PSL
605,Liberal Forum,LIF
614,Democratic Alliance,AD
615,Alignment,AMT
622,Christian Democratic and Flemish,CD&V
623,Justicialist Party,PJ
631,Federal Democratic Union,EDU/UDF
633,Humanist Democratic Centre,cdH
635,Democratic Party,DiKo
638,Renaissance Movement,Tehiya
641,Union of Israel,AI
644,Slovenian Christian Democrats,SKD
645,Party of the Civic Alliance,PAC
651,Christian Democrats,Kd
655,Socialist Party,PS
656,Kotleba People's Party Our Slovakia,ĽSNS
669,Swiss Labour Party,PdAS/PdTS
671,For Fatherland and Freedom,TB
674,United Albanian Right,DBSH
690,Moderate Coalition Party,MSP
696,Alliance for Change,PAN
698,"Democratic, Federalist, Independent",DéFI
699,Nationalist Party,PN
703,Christian Democrats in Finland,KD
705,Christian People's Party,KrF
714,50Plus,50PLUS
719,Socialist Left Party,SV
722,Navarrese People's Union,UPN
723,Brazilian Socialist Party,PSB
724,Socialist Party of Macedonia,SPM
727,Self-Defence of the Polish Republic,SRP
730,Slovak National Party,SNS
732,Legality Movement Party,PLL
736,Japanese Communist Party,JCP
737,Popular Republican Movement,MRP
741,Latvian Social Democratic Alliance,LSDA
742,Italian Socialist Party,PSI
745,Crossroads - non-aligned movement for Zionist Renewal,Tzomet
752,ACT New Zealand,ACT
756,Christian-Democrat and Flemish / New Flemish Alliance,CD+NVA
757,Bulgarian Socialist Party,BSP
759,Party of U,PU
760,Citizens for European Development of Bulgaria,GERB
762,Social Democratic and Labour Party,SDLP
764,Slovenian People's Party,SLS
768,List Di Pietro - Italy of Values,IdV
789,Reform Movement,MR
794,New Democracy,ND
797,Democratic Left,ID
799,Croatian People's Party - Liberal Democrats,HNS
800,Centre Democrats,CD
802,Democratic Party,PD
808,Labour Party,PL
809,Republican Party,Republicans
813,National Alliance,AN
815,National Salvation Party,MSP
819,Albanian Republican Party,PRSH
821,Estonian Reform Party,ER
824,Christian Democratic Party,KDS
827,Pachakutik Plurinational Unity Movement - New Country,MUPP-NP
828,People's Party for Freedom and Democracy,VVD
830,Left Party,V
838,Social Democratic Party of Albania,PSD
840,Agrarian Party of Albania,PASH
842,Libertarian Movement Party,PML
848,Catalan Republican Left,ERC
851,Green Federation,FdV
858,Ecological and Environmental Movement,KOP
863,Electoral Action Solidarity',AWS
867,Democratic Socialist Party,DSP
878,Democrats of the Left,DS
886,Ecologist Party 'The Greens',PEV
888,New Italian Socialist Party,NPSI
894,Democratic Liberal Party,DLP
896,Popular Democratic Union,UDP
898,Social Democratic Party,SDP
901,Finnish Centre,SK
907,New Communist Party,RAKAH
908,"National Coalition Party ""Pro Patria""",RKI
910,Alliance of Free Democrats,SzDSz
911,New Zealand Democratic Party,NZDP
919,Democratic Party,SD
921,Democratic Socialists '70,DS70
924,Progressive Democrats,PD
928,National Renewal,RN
931,Green Party,GPC
932,Independents' Party,DU
934,Italian Popular Party,PPI
940,United Future New Zealand,UF
946,Aragonese Party,PAR
949,Solidarity Party,PS
950,Social Christian Party,PSC
952,Movement for an Autonomous Democracy - Society for Moravia-Silesia,HSD-SMS
953,National Union,IL
962,Christian Democratic Centre / United Christian Democrats,CCD/CDU
963,We Ourselves,SF
964,Progressive Party,F
965,Popular Orthodox Rally,LAOS
975,Liberal Democracy of Slovenia,LDS
982,Movement for Rights and Freedoms,DPS
983,Democratic Party,DS
986,Scottish National Party,SNP
987,Christian Democratic Party,PDC
990,Australian Democrats,AD
1002,The Party of Wales,PC
1004,Conservative Party of Canada,CP
1006,Green Party of Switzerland,GPS/PES
1008,Civic Democratic Alliance,ODA
1009,Democratic Labour Party,PDT
1011,Galician Nationalist Bloc,BNG
1013,National Popular Front,ELAM
1022,Danish People's Party,DF
1024,Party of the Land,CnT
1027,Bulgarian Business Bloc,BBB
1028,Social Democrats' Movement,EDEK
1033,Party for Democratic Prosperity,PDP
1038,Democratic Alliance,DA
1040,Estonian Greens,EER
1043,Latvian Way Union,LC
1044,Left Wing Alliance,VAS
1049,New Zealand Labour Party,Labour
1053,Israel for Immigration,YBA
1055,Soldiers of Destiny,FF
1056,Concord Centre,SC
1067,Welfare Party,RP
1072,Centre Party,Sp
1075,United Torah Judaism,UTJ
1076,Progressive Party of the Working People,AKEL
1077,Coexistence,ESWS
1079,Norwegian Communist Party,NKP
1083,Union for a New Majority - Gaullists/Conservatives,RPF
1096,Finnish People's Democratic Union,SKDL
1099,Green Party of Aotearoa New Zealand,Greens
1102,Christian Historical Union,CHU
1104,Union of Labour,UP
1105,Hungarian Democratic Alliance of Romania,UDMR/RMDSz
1114,Alternative Democratic Reform Party,ADR
1119,New Labour Party,NLP
1123,Protestant People's Party of Switzerland,EVP/PEV
1126,Italian Democratic Socialist Party,PSDI
1131,Institutional Renewal Party of National Action,PRIAN
1134,Justice Party,RF
1136,Left Socialist Party,VS
1138,Green Alternative,GAP
1150,Social Democratic Party,SDE
1156,Social Credit,Socred
1157,Christian Democratic Appeal,CDA
1160,Democratic Left,DIMAR
1163,Democratic Social Party,PDS
1164,Social Democratic League of Workers and Smallholders,TPSL
1166,Serbian Democratic Party,SDS
1173,Liberal Party,V
1178,Reformed Political Party,SGP
1183,United Democratic Forces,ODS
1185,Lithuanian Centre Union,LCS
1190,Motherland Party,ANAP
1193,Liberal and Centre Union,LiCS
1194,Independents' Alliance,LdU/AdI
1195,Democrats for a Strong Bulgaria,DSB
1199,Social Christian Unity Party,PUSC
1204,Liberals,V
1209,Australian Greens,Greens
1215,Alternative for a Republic of Equals,ARI
1219,African National Congress,ANC
1220,Democratic Union for Integration,DUI/BDI
1221,League,L
1224,Liberal Party,FF
1225,Dominican Liberation Party,PLD
1229,Swedish People's Party,RKP/SFP
1231,FDP. The Liberals,FDP/PLR
1234,Labour Party,PvdA
1241,Labor Party,PT
1242,Party of the Democratic Left,SDľ
1246,Independent Republicans | Liberal Democracy,IR|DL
1247,Citizen's Action Party,PAC
1248,Centre Democrats,CDS
1249,People's Alliance,Ab
1251,French Communist Party,PCF
1252,Broad Front,FA
1253,Social Democratic Populist Party,SHP
1271,Union of the Democratic Centre/Centrist Bloc,UCD
1274,Liberals,L
1287,Pirates,Pi
1288,Family of the Irish,FG
1294,Independent Smallholders' Party,FKgP
1299,Estonian National Independence Party,ERSP
1303,Finnish Social Democrats,SSDP
1305,Greater Romania Party,PRM
1310,Left Bloc,BE
1311,Freedom Party of Switzerland,FPS
1324,Basque Solidarity,EA
1325,Social Democratic Party,A
1326,Basque Left,EE
1329,Austrian People's Party,ÖVP
1331,Citizens' Movement,CD
1338,Spanish Socialist Workers' Party,PSOE
1345,New Alliance Party,PANAL
1351,Democratic Revolutionary Party,PRD
1354,Republican Party,CnP
1357,Lithuanian Liberal Union,LLS
1358,Citizens' Rights Movement,RATZ
1359,Social Democratic Party,PSD
1363,Socialist Party,SP
1366,Ulster Unionist Party,UUP
1367,National Liberation Party,PLN
1369,South Tyrolean Peoples Party,SVP
1373,Romanian Ecological Party,PER
1375,Christian Democratic Union,CDU
1384,Austrian Social Democratic Party,SPÖ
1386,Freedom and Solidarity,SaS
1388,Liberal Democrats,LibDems
1390,Catholic People's Party,KVP
1396,The Alliance - Social Democratic Party of Iceland,S
1398,Unity of Labour,AHA
1403,Social Democratic Party,SD
1404,Communist Refoundation Party,PRC
1405,Popular Democracy - Christian Democratic Union,DP - UDC
1408,Hungarian Socialist Party,MSzP
1412,Christian Democratic People's Party,KDNP
1415,Conservative Democratic Party of Switzerland,BDP/PBD
1417,Israel Communist Party,Maki
1419,Colorado Party,PC
1424,Peoples' Union,VU
1428,Quebec Bloc,BQ
1431,Croatian Democratic Union,HDZ
1436,Israeli Labour Party,HaAvoda
1439,German Minority,MN
1441,Dominican Revolutionary Party,PRD
1447,National Religious Party,Mafdal
1448,Socialist Party,SP
1449,Pannella-Sgarbi List,PR
1450,Ecuadorian Roldosist Party,PRE
1451,Romanian National Unity Party,PUNR
1454,Party of Democratic Action,SDA
1459,Christian Union,CU
1461,Italian Liberal Party,PLI
1467,Party for the Animals,PvdD
1472,Centre Democrats,CD
1474,Institutional Revolutionary Party,PRI
1478,Socialist Party,PS
1479,Democratic Party of Slovenia,DSS
1480,Popular Monarchist Party,PPM
1489,General Zionists,GZ
1490,Lithuanian Peasant and Green Union,LVŽS
1494,Greens of Slovenia,ZS
1497,Andalusian Party,PA
1502,German Party,DP
1507,Danish Social-Liberal Party,RV
1508,Social-Democratic League of Macedonia,SDSM
1515,New Clean Government Party,NKP
1516,Labour Party,Labour
1517,Union of Liberals and Leftists,SFVM
1519,Centre Right,CeD
1524,New Liberal Club,NLC
1527,Red-Green Unity List,EL
1528,List Pim Fortuyn,LPF
1531,New Era,JL
1533,The Greens,Groen
1540,Democratic Labor Party,DLP
1545,The Left,LINKE
1554,Green Party,SZ
1556,Coalition Party,KE
1563,Ecologists,ECOLO
1564,Aragonist Council,CHA
1565,Law and Justice,PiS
1567,Conservative Party,Conservatives
1577,Colombian Conservative Party,PCC
1581,Radical Political Party,PPR
1586,Socialist Party Different - Spirit,sp.a-SPIRIT
1597,Hungarian Justice and Life Party,MIÉP
1599,Independent Democratic Union,UDI
1600,Democratic Renewal Party,PRD
1601,Progress Party,FP
1602,Reformed Political League,GPV
1610,National Action Party,MHP
1615,Confederation for an Independent Poland,KPN
1618,New Slovenian Christian People's Party,Nsi
1626,Go Italy,FI
1629,Union for a New Majority - Conservatives/Gaullists,PRL
1630,Inkatha Freedom Party,IFP
1635,Party of Italian Communists,PdCI
1636,Union for Human Rights Party,PBDNJ
1637,Basque Nationalist Party,PNV/EAJ
1647,Communist Party of Luxembourg,KPL/PCL
1650,Politics Can Be Different,LMP
1651,Independent Greeks,ANEL
1655,Freedom,Herut
1657,Association for the Republic - Republican Party of Czechoslovakia,SPR-RSČ
1659,The Greens,GRÜNE
1660,Golden Dawn,XA
1661,Ticino League,LdT
1663,National Democratic Assembly,BaLaD
1664,Homeland,Moledet
1671,Amaiur,Amaiur
1673,Party of Democratic Progress of the Republika Srpska,PDP RS
1682,Democratic Social Movement,DIKKI
1688,Popular Democratic Movement,MDP
1689,True Finns,PS
1691,Alliance of Federation of Young Democrats - Christian Democratic People's Party,FiDeSz-KDNP
1696,National Alliance,AN
1697,Hungarian Democratic Forum,MDF
1702,Latvian Farmers' Union,LZS
1705,United People Alliance,APU
1715,Democratic Party,PD
1716,Maori Party,Maori
1719,Harmony for Latvia - Rebirth of the Economy,SLAT
1726,Hungarian Social Democratic Party,MSzDP
1728,Communist Party of Bohemia and Moravia,KSČM
1729,Albanian Socialist Party,PSS
1731,Christian Social Union,CSU
1739,Liberal Party of Canada,LP
1740,Democratic Left Party,DSP
1744,Liberal Movement,LRLS
1746,Liberal Democratic Party,LDP
1750,Christian Democratic National Peasants Party,PNŢCD
1753,Workers' Party of Belgium,PTB/PVDA
1755,People's Party,TP
1757,Reform Party of Canada,RPC
1758,Union / Centre,UC
1759,Green Liberal Party,GLP
1767,Christian Democratic Centre,CCD
1768,League of Polish Families,LPR
1775,Green Party,Greens
1777,Your Party,YP
1783,Left Radical Party,PRG
1789,Latvian National Independence Movement,LNNK
1790,Alliance Union-PRO,PRO
1793,National Union Attack,ATAKA
1794,Green Party of England and Wales,GPEW
1800,New Union Social Liberals,NS
1804,Democratic Party of Japan,DPJ
1808,Christian Democratic People's Party of Switzerland,CVP/PDC
1816,Alliance 90/The Greens,B90/Gruene
1817,European Party,EVROKO
1819,Swiss Democrats,SD/DS
1823,Liberal Party,PL
1824,New Zealand National Party,National
1963,Green Party,MDG
1966,United National Party,UNP
1968,Flemish Interest,VB
1970,The New Austria and Liberal Forum,NEOS
1976,Alternative for Germany,AfD
1989,Authentic Party of the Mexican Revolution,PARM
1991,Azerbaijan National Independence Party,AMİP
1992,Azerbaijan Popular Front,AXC
1995,New Azerbaijan Party,YAP
1997,Katter's Australian Party,KAT
1998,Liberal Party,LP
2007,Rule of Law,OEK
2009,National Democratic Union,AZhM
2013,Republican Party of Armenia,HHK
2018,Communist Party of Armenia,HKK
2020,Armenian Revolutionary Federation,HHD
2030,Party of Communists of Belarus,PKB
2046,Five Star Movement,M5S
2047,Czech Pirate Party,Piráti
2048,Pirate Party,Þ
2057,National Front for the Salvation of Bulgaria,NFSB
2080,Japan Innovation Party,JIP
2130,Ordinary People and Independent Personalities,OľaNO
2141,ANO 2011,ANO
2148,Citizens Union of Georgia,SMK
2153,Georgian Labour Party,SLP
2159,Industry will save Georgia,MGS
2161,National Democratic Party,EDP
2162,New Rights,AM
2168,Union for the Democratic Revival,DAP
2169,Union of Georgian Traditionalists,KTK
2172,United National Movement,ENM
2175,Serbian Radical Party,SRS
2176,Serbian Renewal Movement,SPO
2178,Socialist Party of Serbia,SPS
2186,Democratic Community of Magyars of Vojvodina,DZVM
2189,Democratic Party,DS
2193,G17plus,G17+
2196,Liberal Democratic Party,LDP
2197,New Democracy,ND
2199,Party for Democratic Activity,PVD/PDD
2203,Alliance of Vojvodina Hungarians,VMSZ
2207,Popular Movement of Ukraine,Rukh
2210,Social Democratic Party of Ukraine,SDPU
2211,Bloc Socialist Party of Ukraine and Peasant Party of Ukraine,SPU-SelPU
2220,Communist Party of Ukraine,KPU
2228,Juliya Tymoshenko Election Bloc,BYuT
2231,Our Ukraine - People's Self-Defense,NU-NS
2235,Agrarian Party of Russia,APR
2236,Communist Party of the Russian Federation,KPRF
2244,Just Russia,SR
2245,Zhirinovsky Bloc,BZ
2247,Our Home - Russia,NDR
2252,Russian United Democratic Party 'Yabloko',Yabloko
2253,Russias Democratic Choice,DVR
2255,Union of Right Forces,SPS
2257,Women of Russia,ZR
2260,Party of Communists of the Republic of Moldova,PCRM
2264,Christian Democratic Peoples Party,PPCD
2265,Democratic Party of Moldova,PDM
2271,Liberal Democratic Party of Moldova,PLDM
2272,Liberal Party,PL
2280,Brothers of Italy,FDI
2305,Democratic Party,DP
2306,Liberty Forward Party,LFP
2307,New Frontier Party,NFP
2346,New Serbia,NS
2347,Croatian Democratic Assembly of Slavonia and Baranja,HDSSB
2348,Independent Democratic Serbian Party,SDSS
2415,Autonomy Liberty Democracy Aosta Valley,ALD
2447,New Social Democratic Party,NSDP
2449,Liberal Party,LP
2458,Together 2014 -Dialogue for Hungary Electoral Alliance,E14-PM
2517,Bright Future,Bf
2538,Commitment-Q,PRC
2545,United Liberal Party,ULD
2546,Democratic Labour Party,DLP
2547,Democratic Party - including Platform Party,DP
2548,Millenium Democratic Party,MDP
2553,Party of Socialists of the Republic of Moldova,PSRM
2645,Independent Social Alliance,ASI
2874,Broad Front for Democracy,FAD
2915,Yugoslav Left,JUL
2988,Georgian Dream,GD
3098,Modern Centre Party,SMC
3104,Socialist People's Party of Montenegro,SNP CG
3114,Party of Alenka Bratušek,SAB
3128,There is a Future,YA
3131,Meretz,MERETZ
3143,Croatian Labourists - Labour Party,HL
3162,Democratic Party of Socialists of Montenegro,DPS CG
3163,For a Better Life,DZB
3164,Liberal Alliance of Montenegro,LSCG
3168,Croatian Democratic Union 1990,HDZ 1990
3171,Alliance for a Better Future of Bosnia and Herzegovina,SBB BiH
3173,Justice and Reconciliation Party,SPP
3176,Party of Democratic Action,SDA
3177,Serbian Progressive Party,SNS
3185,Social Democratic Party of Montenegro,SDP CG
3187,Alternative for Bulgarian Revival,ABV
3194,Latvian Association of Regions,LRa
3203,We Can,Podemos
3210,People's Movement Party,PMP
3217,Citizens - Party of the Citizens,C's
3218,Voice,VOX
3225,Free,L
3229,Union of Democrats and Independents,UDI
3252,New Serbian Democracy,NOVA
3255,Bosniak Party,BS
3266,All-Ukrainian Union 'Fatherland',Batkivshchyna
3267,All-Ukrainian Union Freedom',ВО/Свобода
3271,Free Party,EVA
3273,All of Us,Kulanu
3645,People's Party,NS
3671,New Space,NE
3698,Red Party,R
3783,Heritage,HePa
3890,Armenia Alliance,HD
3904,alliance: HDZ-HK~HNZ,HDZ-HK~HNZ
3908,Gergiovden-VMRO / VMRO-Bulgarian National Movement,G-VMRO; VMRO-BND
3915,Approve Dignity,FA
3916,Colombian Coalition,CC
3955,Labour - Gesher,Avoda-Gesher
3973,Coalition Civic Option for Macedonia,GROM
3979,Commitment for Mexico,AM
3982,Democratic Coalition,DK
3995,Opposition Action Alliance,AAO
3997,Alliance Possible Peru,PP
4011,We must,MORAMO
4020,alliance: CP / VLSSP,CP / VLSSP
4044,Proud and Sovereign Fatherland Alliance Movement,Alianza PAIS
4070,Alternativ,A
4094,Conservative People's Party of Estonia,EKRE
4182,Front for Loyalty and Union of the Democratic Centre,FPL + UCeDé
4205,Prosperous Armenia,BHK
4214,Peoples Force,FP
4258,Internal Macedonian Revolutionary Organization-People's Party,VMRO-NP
4269,Creating Opportunities,CREO
4364,Liberal Democracy,DL
4373,Justice Party,AP
4400,Democratic and Popular Union,UDP
4405,Liberal Party,PR
4418,National Integration Party,PIN
4546,Congress of the People,COPE
4547,Democratic Party,DP
4548,Economic Freedom Fighters,EEF
4628,Republicans,Rep
4630,Modern,.N
4631,Kukiz'15,K
4714,United Left,ZL
4758,Democratic Party,DP
4766,Movement for Changes,PzP
4778,European Solidarity,BPP
4779,Opposition Bloc,OB
4795,Democratic Convergence of Catalonia,CDC
4852,Democratic Alliance - 19th of April Movement,DA M19
4865,Bridge of Independent Lists,MOST
4866,People's Party - Reformists,NS-R
4870,Social Democrats,DS
4873,We Are Family,SR
5414,Lanka Equal Society Party,LSS
5453,Centre Alliance,NXT
5454,Reform Party,Vidreisn
5468,United Reform Action,URA
5486,Serbian People's Party,SNS
5553,Front for a Country in Solidarity,FREPASO
5650,Europe Ecology - The Greens,EELV
5848,Simeon II Coalition,KSII
5852,Peoples Party,FlF
5855,Forum for Democracy,FvD
5856,DENK,DENK
5857,Renaissance,REM|R
5858,Indomitable France,FI
5879,Democratic Center,CD
5926,Green Alliance,AV
5939,Portugal Ahead,PàF
5969,Save Romania Union,USR
5976,Social Democratic Party 'Harmony',SDPS
6042,Peoples' Democratic Party,HDP
6087,Democratic Front,DF
6110,National Regeneration Movement,MORENA
6113,Social Encounter Party,PES
6114,Alliance of Patriots of Georgia,APG
6125,Freedom and Direct Democracy,SPD
6131,The Left,L
6135,Constitutional Democratic Party of Japan,CDP
6150,Democratic Movement of Serbia,DEPOS
6159,Civic Coalition,CC
6560,Justice Party,JP
6561,Democratic Party,DPK
6648,Federal Peronism / Dissident Peronism,PF-PJ
6691,Communist Party of Sri Lanka,CP
6804,Lithuanian Christian Democratic Party,LKDP
7049,Together for Change,JxC
7339,The New Right,NB
7348,European Realistic Disobedience Front,MeRA25
7421,Together for Catalonia,JxCat
7565,Centre Party,M
7599,Blue and White,KL
7619,National Alliance,NA
7909,Movement for Change,KINAL
7912,Joint List,JL
8031,In Common We Can,ECP
8054,Democrats of the Left,DS
8058,Forza Italia,FI
8168,The Republicans,LR
8176,Geneva Citizens' Movement,MCG
8182,Enough,CH
8241,SolidarityPeople Before Profit,PBPS
8393,Development/For!,AP!
8640,Liberal Iniciative,IL
8842,Coalition of the Social Democratic Party of Croatia and the Croatian Social-liberal Party,SDP-HSLS
9001,Ivica Dacic - Prime Minister of Serbia,SPS/JS/ZS
9095,Rightwards,Y
218,Polo Democratico Alternativo,PDA
284,Unified Democratic Coalition,CDU
568,Nationalist Revolutionary Movement,MNR
751,SNK European Democrats,SNK-ED
1036,Likud,Likud
1060,Republican People's Party,CHP
1308,Social Democratic Centre,CDS-PP
1665,Coalition for Bulgaria,BSP
1674,VMRO-DPMNE,VMRO-DPMNE
1724,Radical Civic Union,UCR
1985,Popular Socialist Party,PPS
2234,Party of Regions,PR
2256,United Russia,ER
3677,Jewish Home,HaBayit
4717,Democratic Front,DF
7031,Left Ecology Freedom,SEL
1 partyfacts_id party_name_english party_name_short
2 3 Social Democratic Party SDP
3 4 We Ourselves SF
4 6 Liberal-Socialists of Chile UCC
5 10 The Greens B90/Gruene
6 14 Radical Party PR
7 18 Estonian United People’s Party EÜRP
8 29 Social Democratic Party of Switzerland SPS/PSS
9 34 Communist Party PCI
10 42 Movement for a Better Hungary Jobbik
11 43 List Dedecker LDD
12 45 Democrats 66 D’66
13 48 Communist Party of Greece KKE
14 49 Open Flemish Liberals and Democrats openVLD
15 50 South Schleswig Voters’ Union SSW
16 53 Christian Democrats K
17 54 Party for Democracy PPD
18 57 Democratic Left Alliance SLD
19 59 Democratic Change CD
20 63 Christian Democratic Movement KDH
21 64 Lithuanian Social Democratic Party LSDP
22 72 Croatian Peasant Party HSS
23 78 Democratic Centre DC
24 81 Canarian Coalition-New Canaries CCa-PNC-NC
25 82 Union of the Democratic Centre EDIK
26 86 The Left. Party of Democratic Socialism L-PDS
27 96 Slovenian National Party SNS
28 97 People-Animals-Nature PAN
29 101 Progress Party FrP
30 102 Labour Party DP
31 109 Worker's Party of the Land of Israel Mapai
32 110 Estonian People's Union ERL
33 118 National Party PN
34 120 Social Democratic Party PSD
35 150 Progressive Party Progressive
36 152 New Democratic Party NDP
37 155 People's Democratic Party PDP
38 162 Communist Party of Chile PC
39 163 Anti-Revolutionary Party ARP
40 169 United Workers' Party MAPAM
41 171 Liberal Democratic Party LDP
42 172 Reformatory Political Federation RPF
43 176 For Human Rights in a United Latvia PCTVL
44 177 Czech Social Democratic Party ČSSD
45 190 Democratic Party of Albania PD
46 193 Homeland Union TS
47 194 Social Christian Reformist Party PRSC
48 199 Centre Party CP
49 208 Workers' Party WP
50 209 Humanist Party PH
51 212 Liberal Alliance NY
52 214 Croatian Social-Liberal Party HSLS
53 216 Democratic Revolutionary Party PRD
54 223 Tradition, Responsibility, Prosperity 09 TOP09
55 225 Party of Brazilian Social Democracy PSDB
56 232 Progressive Conservative Party PCP
57 235 Israel is Our Home YB
58 237 Order and Justice PTT
59 246 Patriotic Society Party PSP
60 247 United Left IU
61 249 Liberal People's Party LKP
62 253 Croatian Party of Rights HSP
63 257 National Restoration Party PRN
64 258 Bridge Most-Hid
65 259 Italian Renewal RI
66 263 Bulgarian People's Union BNS
67 271 The Left DL
68 275 Party of Liberty and Progress PVV/PLP
69 276 Social Democratic Party PSD
70 277 Danish Communist Party DKP
71 279 Daisy - Democracy is Freedom M-DL
72 281 Walloon Rally RW
73 285 Liberal National Party of Queensland LNP
74 292 Alliance of Independent Social Democrats SNSD
75 298 Party of Freedom PVV
76 300 Democratic Party DP/PD
77 304 Christian Democratic Union KDS
78 306 Justice and Development Party AKP
79 308 Swiss People's Party SVP/UDC
80 310 Portuguese Communist Party PCP
81 311 Direction-Social Democracy Smer
82 312 Political Spring Pola
83 329 Socialist People's Party SF
84 335 Democratic Unionist Party DUP
85 338 Nationalist Party of Australia NAT
86 340 Communist Party of Slovakia KSS
87 349 Party of the Hungarian Coalition SMK/MKP
88 351 True Path Party DYP
89 352 People's New Party PNP
90 356 Workers' Party PT
91 357 Party for Bosnia and Herzegovina SBiH
92 362 Colombian Liberal Party PLC
93 363 Independence Party Sj
94 378 Ecologist Greens OP
95 379 Social Democratic Party SD
96 382 Liberal Party PL
97 383 Social Democratic Party of Germany SPD
98 390 Christian Democratic Party PDC
99 394 Italian Republican Party PRI
100 405 Francophone Christian Social Party and Flemish Christian People's Party PSC/CVP
101 409 Sweden Democrats SD
102 421 Democratic Front for Peace and Equality HADASH
103 424 Australian Labor Party ALP
104 431 Democratic Party of Albanians DPA/PDSh
105 432 Democratic Party Democrats
106 433 National Front FN
107 434 Progressive and Independent Liberal Party ILP
108 437 Social Democrat Radical Party PRSD
109 441 People's Party PP
110 442 Movement towards Socialism - Political Instrument for the Sovereignty of the Peoples MAS-IPSP
111 444 Liberal Party of Switzerland LPS/PLS
112 446 Mexican Green Ecologist Party PVEM
113 447 United Arab List Ra'am
114 448 Labour Party Ap
115 455 Sephardi Torah Guardians Shas
116 456 Green Ecology Party MP
117 457 Left Green Movement VGF
118 458 Brazilian Labour Party PTB
119 459 Communist Party of the Netherlands CPN
120 463 Austrian Freedom Party FPÖ
121 466 Civic Democratic Party ODS
122 467 Democratic Party of Pensioners of Slovenia DeSUS
123 469 Democratic Coalition DK
124 472 Slovenian Democratic Party SDS
125 474 For Real Zares
126 479 Green Union VL
127 480 Belgian Socialist Party BSP/PSB
128 481 National Liberal Party PNL
129 484 Forward Kadima
130 487 Social Democratic Labour Party SAP
131 491 Res Publica Party ERP
132 492 Social Democratic Federation SDF
133 495 National Coalition KK
134 496 Democratic Movement MoDem
135 500 Francophone Socialist Party PS
136 503 Conservative Party H
137 506 National Alliance 'All For Latvia! - For Fatherland and Freedom/LNNK' NA
138 509 Rally for the Republic RPR
139 513 Istrian Democratic Assembly IDS
140 514 Popular Action AP
141 516 Progressive Left Coalition SYN
142 525 Women's Alliance Kv
143 528 Green! Groen!
144 529 Peruvian Aprista Party PAP
145 533 Estonian Center Party K
146 536 Conservative People's Party KF
147 537 Social Democratic Party of Bosnia and Herzegovina SDP BiH
148 539 Christian Social People's Party CSV/PCS
149 540 Liberal Party Liberal
150 546 Lithuanian Democratic Labour Party LDDP
151 553 Flemish Bloc VB
152 554 Liberal Reformation Party PRL
153 556 Election Action of Lithuania's Poles LLRA/AWPL
154 557 Change SHINUI
155 560 Movement for a Democratic Slovakia HZDS
156 561 Party of Radical Change PCR
157 562 Labour Party Labour
158 563 Democratic Coalition DISY
159 573 Free Democratic Party FDP
160 575 Union for Peru UPP
161 591 New Zealand First Party NZF
162 594 Democratic Alliance Party AD
163 598 Proletarian Unity Party for Communism The Manifesto + Proletarian Unity Party PdUP
164 599 Alliance for the Future of Austria BZÖ
165 601 United Kingdom Independence Party UKIP
166 602 Polish Peasants' Party PSL
167 605 Liberal Forum LIF
168 614 Democratic Alliance AD
169 615 Alignment AMT
170 622 Christian Democratic and Flemish CD&V
171 623 Justicialist Party PJ
172 631 Federal Democratic Union EDU/UDF
173 633 Humanist Democratic Centre cdH
174 635 Democratic Party DiKo
175 638 Renaissance Movement Tehiya
176 641 Union of Israel AI
177 644 Slovenian Christian Democrats SKD
178 645 Party of the Civic Alliance PAC
179 651 Christian Democrats Kd
180 655 Socialist Party PS
181 656 Kotleba – People's Party Our Slovakia ĽSNS
182 669 Swiss Labour Party PdAS/PdTS
183 671 For Fatherland and Freedom TB
184 674 United Albanian Right DBSH
185 690 Moderate Coalition Party MSP
186 696 Alliance for Change PAN
187 698 Democratic, Federalist, Independent DéFI
188 699 Nationalist Party PN
189 703 Christian Democrats in Finland KD
190 705 Christian People's Party KrF
191 714 50Plus 50PLUS
192 719 Socialist Left Party SV
193 722 Navarrese People's Union UPN
194 723 Brazilian Socialist Party PSB
195 724 Socialist Party of Macedonia SPM
196 727 Self-Defence of the Polish Republic SRP
197 730 Slovak National Party SNS
198 732 Legality Movement Party PLL
199 736 Japanese Communist Party JCP
200 737 Popular Republican Movement MRP
201 741 Latvian Social Democratic Alliance LSDA
202 742 Italian Socialist Party PSI
203 745 Crossroads - non-aligned movement for Zionist Renewal Tzomet
204 752 ACT New Zealand ACT
205 756 Christian-Democrat and Flemish / New Flemish Alliance CD+NVA
206 757 Bulgarian Socialist Party BSP
207 759 Party of U PU
208 760 Citizens for European Development of Bulgaria GERB
209 762 Social Democratic and Labour Party SDLP
210 764 Slovenian People's Party SLS
211 768 List Di Pietro - Italy of Values IdV
212 789 Reform Movement MR
213 794 New Democracy ND
214 797 Democratic Left ID
215 799 Croatian People's Party - Liberal Democrats HNS
216 800 Centre Democrats CD
217 802 Democratic Party PD
218 808 Labour Party PL
219 809 Republican Party Republicans
220 813 National Alliance AN
221 815 National Salvation Party MSP
222 819 Albanian Republican Party PRSH
223 821 Estonian Reform Party ER
224 824 Christian Democratic Party KDS
225 827 Pachakutik Plurinational Unity Movement - New Country MUPP-NP
226 828 People's Party for Freedom and Democracy VVD
227 830 Left Party V
228 838 Social Democratic Party of Albania PSD
229 840 Agrarian Party of Albania PASH
230 842 Libertarian Movement Party PML
231 848 Catalan Republican Left ERC
232 851 Green Federation FdV
233 858 Ecological and Environmental Movement KOP
234 863 Electoral Action ‘Solidarity' AWS
235 867 Democratic Socialist Party DSP
236 878 Democrats of the Left DS
237 886 Ecologist Party 'The Greens' PEV
238 888 New Italian Socialist Party NPSI
239 894 Democratic Liberal Party DLP
240 896 Popular Democratic Union UDP
241 898 Social Democratic Party SDP
242 901 Finnish Centre SK
243 907 New Communist Party RAKAH
244 908 National Coalition Party "Pro Patria" RKI
245 910 Alliance of Free Democrats SzDSz
246 911 New Zealand Democratic Party NZDP
247 919 Democratic Party SD
248 921 Democratic Socialists '70 DS‘70
249 924 Progressive Democrats PD
250 928 National Renewal RN
251 931 Green Party GPC
252 932 Independents' Party DU
253 934 Italian Popular Party PPI
254 940 United Future New Zealand UF
255 946 Aragonese Party PAR
256 949 Solidarity Party PS
257 950 Social Christian Party PSC
258 952 Movement for an Autonomous Democracy - Society for Moravia-Silesia HSD-SMS
259 953 National Union IL
260 962 Christian Democratic Centre / United Christian Democrats CCD/CDU
261 963 We Ourselves SF
262 964 Progressive Party F
263 965 Popular Orthodox Rally LAOS
264 975 Liberal Democracy of Slovenia LDS
265 982 Movement for Rights and Freedoms DPS
266 983 Democratic Party DS
267 986 Scottish National Party SNP
268 987 Christian Democratic Party PDC
269 990 Australian Democrats AD
270 1002 The Party of Wales PC
271 1004 Conservative Party of Canada CP
272 1006 Green Party of Switzerland GPS/PES
273 1008 Civic Democratic Alliance ODA
274 1009 Democratic Labour Party PDT
275 1011 Galician Nationalist Bloc BNG
276 1013 National Popular Front ELAM
277 1022 Danish People's Party DF
278 1024 Party of the Land CnT
279 1027 Bulgarian Business Bloc BBB
280 1028 Social Democrats' Movement EDEK
281 1033 Party for Democratic Prosperity PDP
282 1038 Democratic Alliance DA
283 1040 Estonian Greens EER
284 1043 Latvian Way Union LC
285 1044 Left Wing Alliance VAS
286 1049 New Zealand Labour Party Labour
287 1053 Israel for Immigration YBA
288 1055 Soldiers of Destiny FF
289 1056 Concord Centre SC
290 1067 Welfare Party RP
291 1072 Centre Party Sp
292 1075 United Torah Judaism UTJ
293 1076 Progressive Party of the Working People AKEL
294 1077 Coexistence ESWS
295 1079 Norwegian Communist Party NKP
296 1083 Union for a New Majority - Gaullists/Conservatives RPF
297 1096 Finnish People's Democratic Union SKDL
298 1099 Green Party of Aotearoa New Zealand Greens
299 1102 Christian Historical Union CHU
300 1104 Union of Labour UP
301 1105 Hungarian Democratic Alliance of Romania UDMR/RMDSz
302 1114 Alternative Democratic Reform Party ADR
303 1119 New Labour Party NLP
304 1123 Protestant People's Party of Switzerland EVP/PEV
305 1126 Italian Democratic Socialist Party PSDI
306 1131 Institutional Renewal Party of National Action PRIAN
307 1134 Justice Party RF
308 1136 Left Socialist Party VS
309 1138 Green Alternative GAP
310 1150 Social Democratic Party SDE
311 1156 Social Credit Socred
312 1157 Christian Democratic Appeal CDA
313 1160 Democratic Left DIMAR
314 1163 Democratic Social Party PDS
315 1164 Social Democratic League of Workers and Smallholders TPSL
316 1166 Serbian Democratic Party SDS
317 1173 Liberal Party V
318 1178 Reformed Political Party SGP
319 1183 United Democratic Forces ODS
320 1185 Lithuanian Centre Union LCS
321 1190 Motherland Party ANAP
322 1193 Liberal and Centre Union LiCS
323 1194 Independents' Alliance LdU/AdI
324 1195 Democrats for a Strong Bulgaria DSB
325 1199 Social Christian Unity Party PUSC
326 1204 Liberals V
327 1209 Australian Greens Greens
328 1215 Alternative for a Republic of Equals ARI
329 1219 African National Congress ANC
330 1220 Democratic Union for Integration DUI/BDI
331 1221 League L
332 1224 Liberal Party FF
333 1225 Dominican Liberation Party PLD
334 1229 Swedish People's Party RKP/SFP
335 1231 FDP. The Liberals FDP/PLR
336 1234 Labour Party PvdA
337 1241 Labor Party PT
338 1242 Party of the Democratic Left SDľ
339 1246 Independent Republicans | Liberal Democracy IR|DL
340 1247 Citizen's Action Party PAC
341 1248 Centre Democrats CDS
342 1249 People's Alliance Ab
343 1251 French Communist Party PCF
344 1252 Broad Front FA
345 1253 Social Democratic Populist Party SHP
346 1271 Union of the Democratic Centre/Centrist Bloc UCD
347 1274 Liberals L
348 1287 Pirates Pi
349 1288 Family of the Irish FG
350 1294 Independent Smallholders' Party FKgP
351 1299 Estonian National Independence Party ERSP
352 1303 Finnish Social Democrats SSDP
353 1305 Greater Romania Party PRM
354 1310 Left Bloc BE
355 1311 Freedom Party of Switzerland FPS
356 1324 Basque Solidarity EA
357 1325 Social Democratic Party A
358 1326 Basque Left EE
359 1329 Austrian People's Party ÖVP
360 1331 Citizens' Movement CD
361 1338 Spanish Socialist Workers' Party PSOE
362 1345 New Alliance Party PANAL
363 1351 Democratic Revolutionary Party PRD
364 1354 Republican Party CnP
365 1357 Lithuanian Liberal Union LLS
366 1358 Citizens' Rights Movement RATZ
367 1359 Social Democratic Party PSD
368 1363 Socialist Party SP
369 1366 Ulster Unionist Party UUP
370 1367 National Liberation Party PLN
371 1369 South Tyrolean People’s Party SVP
372 1373 Romanian Ecological Party PER
373 1375 Christian Democratic Union CDU
374 1384 Austrian Social Democratic Party SPÖ
375 1386 Freedom and Solidarity SaS
376 1388 Liberal Democrats LibDems
377 1390 Catholic People's Party KVP
378 1396 The Alliance - Social Democratic Party of Iceland S
379 1398 Unity of Labour AHA
380 1403 Social Democratic Party SD
381 1404 Communist Refoundation Party PRC
382 1405 Popular Democracy - Christian Democratic Union DP - UDC
383 1408 Hungarian Socialist Party MSzP
384 1412 Christian Democratic People's Party KDNP
385 1415 Conservative Democratic Party of Switzerland BDP/PBD
386 1417 Israel Communist Party Maki
387 1419 Colorado Party PC
388 1424 Peoples' Union VU
389 1428 Quebec Bloc BQ
390 1431 Croatian Democratic Union HDZ
391 1436 Israeli Labour Party HaAvoda
392 1439 German Minority MN
393 1441 Dominican Revolutionary Party PRD
394 1447 National Religious Party Mafdal
395 1448 Socialist Party SP
396 1449 Pannella-Sgarbi List PR
397 1450 Ecuadorian Roldosist Party PRE
398 1451 Romanian National Unity Party PUNR
399 1454 Party of Democratic Action SDA
400 1459 Christian Union CU
401 1461 Italian Liberal Party PLI
402 1467 Party for the Animals PvdD
403 1472 Centre Democrats CD
404 1474 Institutional Revolutionary Party PRI
405 1478 Socialist Party PS
406 1479 Democratic Party of Slovenia DSS
407 1480 Popular Monarchist Party PPM
408 1489 General Zionists GZ
409 1490 Lithuanian Peasant and Green Union LVŽS
410 1494 Greens of Slovenia ZS
411 1497 Andalusian Party PA
412 1502 German Party DP
413 1507 Danish Social-Liberal Party RV
414 1508 Social-Democratic League of Macedonia SDSM
415 1515 New Clean Government Party NKP
416 1516 Labour Party Labour
417 1517 Union of Liberals and Leftists SFVM
418 1519 Centre Right CeD
419 1524 New Liberal Club NLC
420 1527 Red-Green Unity List EL
421 1528 List Pim Fortuyn LPF
422 1531 New Era JL
423 1533 The Greens Groen
424 1540 Democratic Labor Party DLP
425 1545 The Left LINKE
426 1554 Green Party SZ
427 1556 Coalition Party KE
428 1563 Ecologists ECOLO
429 1564 Aragonist Council CHA
430 1565 Law and Justice PiS
431 1567 Conservative Party Conservatives
432 1577 Colombian Conservative Party PCC
433 1581 Radical Political Party PPR
434 1586 Socialist Party Different - Spirit sp.a-SPIRIT
435 1597 Hungarian Justice and Life Party MIÉP
436 1599 Independent Democratic Union UDI
437 1600 Democratic Renewal Party PRD
438 1601 Progress Party FP
439 1602 Reformed Political League GPV
440 1610 National Action Party MHP
441 1615 Confederation for an Independent Poland KPN
442 1618 New Slovenian Christian People's Party Nsi
443 1626 Go Italy FI
444 1629 Union for a New Majority - Conservatives/Gaullists PRL
445 1630 Inkatha Freedom Party IFP
446 1635 Party of Italian Communists PdCI
447 1636 Union for Human Rights Party PBDNJ
448 1637 Basque Nationalist Party PNV/EAJ
449 1647 Communist Party of Luxembourg KPL/PCL
450 1650 Politics Can Be Different LMP
451 1651 Independent Greeks ANEL
452 1655 Freedom Herut
453 1657 Association for the Republic - Republican Party of Czechoslovakia SPR-RSČ
454 1659 The Greens GRÜNE
455 1660 Golden Dawn XA
456 1661 Ticino League LdT
457 1663 National Democratic Assembly BaLaD
458 1664 Homeland Moledet
459 1671 Amaiur Amaiur
460 1673 Party of Democratic Progress of the Republika Srpska PDP RS
461 1682 Democratic Social Movement DIKKI
462 1688 Popular Democratic Movement MDP
463 1689 True Finns PS
464 1691 Alliance of Federation of Young Democrats - Christian Democratic People's Party FiDeSz-KDNP
465 1696 National Alliance AN
466 1697 Hungarian Democratic Forum MDF
467 1702 Latvian Farmers' Union LZS
468 1705 United People Alliance APU
469 1715 Democratic Party PD
470 1716 Maori Party Maori
471 1719 Harmony for Latvia - Rebirth of the Economy SLAT
472 1726 Hungarian Social Democratic Party MSzDP
473 1728 Communist Party of Bohemia and Moravia KSČM
474 1729 Albanian Socialist Party PSS
475 1731 Christian Social Union CSU
476 1739 Liberal Party of Canada LP
477 1740 Democratic Left Party DSP
478 1744 Liberal Movement LRLS
479 1746 Liberal Democratic Party LDP
480 1750 Christian Democratic National Peasants’ Party PNŢCD
481 1753 Workers' Party of Belgium PTB/PVDA
482 1755 People's Party TP
483 1757 Reform Party of Canada RPC
484 1758 Union / Centre UC
485 1759 Green Liberal Party GLP
486 1767 Christian Democratic Centre CCD
487 1768 League of Polish Families LPR
488 1775 Green Party Greens
489 1777 Your Party YP
490 1783 Left Radical Party PRG
491 1789 Latvian National Independence Movement LNNK
492 1790 Alliance Union-PRO PRO
493 1793 National Union Attack ATAKA
494 1794 Green Party of England and Wales GPEW
495 1800 New Union Social Liberals NS
496 1804 Democratic Party of Japan DPJ
497 1808 Christian Democratic People's Party of Switzerland CVP/PDC
498 1816 Alliance 90/The Greens B90/Gruene
499 1817 European Party EVROKO
500 1819 Swiss Democrats SD/DS
501 1823 Liberal Party PL
502 1824 New Zealand National Party National
503 1963 Green Party MDG
504 1966 United National Party UNP
505 1968 Flemish Interest VB
506 1970 The New Austria and Liberal Forum NEOS
507 1976 Alternative for Germany AfD
508 1989 Authentic Party of the Mexican Revolution PARM
509 1991 Azerbaijan National Independence Party AMİP
510 1992 Azerbaijan Popular Front AXC
511 1995 New Azerbaijan Party YAP
512 1997 Katter's Australian Party KAT
513 1998 Liberal Party LP
514 2007 Rule of Law OEK
515 2009 National Democratic Union AZhM
516 2013 Republican Party of Armenia HHK
517 2018 Communist Party of Armenia HKK
518 2020 Armenian Revolutionary Federation HHD
519 2030 Party of Communists of Belarus PKB
520 2046 Five Star Movement M5S
521 2047 Czech Pirate Party Piráti
522 2048 Pirate Party Þ
523 2057 National Front for the Salvation of Bulgaria NFSB
524 2080 Japan Innovation Party JIP
525 2130 Ordinary People and Independent Personalities OľaNO
526 2141 ANO 2011 ANO
527 2148 Citizens’ Union of Georgia SMK
528 2153 Georgian Labour Party SLP
529 2159 Industry will save Georgia MGS
530 2161 National Democratic Party EDP
531 2162 New Rights AM
532 2168 Union for the Democratic Revival DAP
533 2169 Union of Georgian Traditionalists KTK
534 2172 United National Movement ENM
535 2175 Serbian Radical Party SRS
536 2176 Serbian Renewal Movement SPO
537 2178 Socialist Party of Serbia SPS
538 2186 Democratic Community of Magyars of Vojvodina DZVM
539 2189 Democratic Party DS
540 2193 G17plus G17+
541 2196 Liberal Democratic Party LDP
542 2197 New Democracy ND
543 2199 Party for Democratic Activity PVD/PDD
544 2203 Alliance of Vojvodina Hungarians VMSZ
545 2207 Popular Movement of Ukraine Rukh
546 2210 Social Democratic Party of Ukraine SDPU
547 2211 Bloc Socialist Party of Ukraine and Peasant Party of Ukraine SPU-SelPU
548 2220 Communist Party of Ukraine KPU
549 2228 Juliya Tymoshenko Election Bloc BYuT
550 2231 Our Ukraine - People's Self-Defense NU-NS
551 2235 Agrarian Party of Russia APR
552 2236 Communist Party of the Russian Federation KPRF
553 2244 Just Russia SR
554 2245 Zhirinovsky Bloc BZ
555 2247 Our Home - Russia NDR
556 2252 Russian United Democratic Party 'Yabloko' Yabloko
557 2253 Russia’s Democratic Choice DVR
558 2255 Union of Right Forces SPS
559 2257 Women of Russia ZR
560 2260 Party of Communists of the Republic of Moldova PCRM
561 2264 Christian Democratic People’s Party PPCD
562 2265 Democratic Party of Moldova PDM
563 2271 Liberal Democratic Party of Moldova PLDM
564 2272 Liberal Party PL
565 2280 Brothers of Italy FDI
566 2305 Democratic Party DP
567 2306 Liberty Forward Party LFP
568 2307 New Frontier Party NFP
569 2346 New Serbia NS
570 2347 Croatian Democratic Assembly of Slavonia and Baranja HDSSB
571 2348 Independent Democratic Serbian Party SDSS
572 2415 Autonomy Liberty Democracy Aosta Valley ALD
573 2447 New Social Democratic Party NSDP
574 2449 Liberal Party LP
575 2458 Together 2014 -Dialogue for Hungary Electoral Alliance E14-PM
576 2517 Bright Future Bf
577 2538 Commitment-Q PRC
578 2545 United Liberal Party ULD
579 2546 Democratic Labour Party DLP
580 2547 Democratic Party - including Platform Party DP
581 2548 Millenium Democratic Party MDP
582 2553 Party of Socialists of the Republic of Moldova PSRM
583 2645 Independent Social Alliance ASI
584 2874 Broad Front for Democracy FAD
585 2915 Yugoslav Left JUL
586 2988 Georgian Dream GD
587 3098 Modern Centre Party SMC
588 3104 Socialist People's Party of Montenegro SNP CG
589 3114 Party of Alenka Bratušek SAB
590 3128 There is a Future YA
591 3131 Meretz MERETZ
592 3143 Croatian Labourists - Labour Party HL
593 3162 Democratic Party of Socialists of Montenegro DPS CG
594 3163 For a Better Life DZB
595 3164 Liberal Alliance of Montenegro LSCG
596 3168 Croatian Democratic Union 1990 HDZ 1990
597 3171 Alliance for a Better Future of Bosnia and Herzegovina SBB BiH
598 3173 Justice and Reconciliation Party SPP
599 3176 Party of Democratic Action SDA
600 3177 Serbian Progressive Party SNS
601 3185 Social Democratic Party of Montenegro SDP CG
602 3187 Alternative for Bulgarian Revival ABV
603 3194 Latvian Association of Regions LRa
604 3203 We Can Podemos
605 3210 People's Movement Party PMP
606 3217 Citizens - Party of the Citizens C's
607 3218 Voice VOX
608 3225 Free L
609 3229 Union of Democrats and Independents UDI
610 3252 New Serbian Democracy NOVA
611 3255 Bosniak Party BS
612 3266 All-Ukrainian Union 'Fatherland' Batkivshchyna
613 3267 All-Ukrainian Union ‘Freedom' ВО/Свобода
614 3271 Free Party EVA
615 3273 All of Us Kulanu
616 3645 People's Party NS
617 3671 New Space NE
618 3698 Red Party R
619 3783 Heritage HePa
620 3890 Armenia Alliance HD
621 3904 alliance: HDZ-HK~HNZ HDZ-HK~HNZ
622 3908 Gergiovden-VMRO / VMRO-Bulgarian National Movement G-VMRO; VMRO-BND
623 3915 Approve Dignity FA
624 3916 Colombian Coalition CC
625 3955 Labour - Gesher Avoda-Gesher
626 3973 Coalition Civic Option for Macedonia GROM
627 3979 Commitment for Mexico AM
628 3982 Democratic Coalition DK
629 3995 Opposition Action Alliance AAO
630 3997 Alliance Possible Peru PP
631 4011 We must MORAMO
632 4020 alliance: CP / VLSSP CP / VLSSP
633 4044 Proud and Sovereign Fatherland Alliance Movement Alianza PAIS
634 4070 Alternativ A
635 4094 Conservative People's Party of Estonia EKRE
636 4182 Front for Loyalty and Union of the Democratic Centre FPL + UCeDé
637 4205 Prosperous Armenia BHK
638 4214 People’s Force FP
639 4258 Internal Macedonian Revolutionary Organization-People's Party VMRO-NP
640 4269 Creating Opportunities CREO
641 4364 Liberal Democracy DL
642 4373 Justice Party AP
643 4400 Democratic and Popular Union UDP
644 4405 Liberal Party PR
645 4418 National Integration Party PIN
646 4546 Congress of the People COPE
647 4547 Democratic Party DP
648 4548 Economic Freedom Fighters EEF
649 4628 Republicans Rep
650 4630 Modern .N
651 4631 Kukiz'15 K
652 4714 United Left ZL
653 4758 Democratic Party DP
654 4766 Movement for Changes PzP
655 4778 European Solidarity BPP
656 4779 Opposition Bloc OB
657 4795 Democratic Convergence of Catalonia CDC
658 4852 Democratic Alliance - 19th of April Movement DA M19
659 4865 Bridge of Independent Lists MOST
660 4866 People's Party - Reformists NS-R
661 4870 Social Democrats DS
662 4873 We Are Family SR
663 5414 Lanka Equal Society Party LSS
664 5453 Centre Alliance NXT
665 5454 Reform Party Vidreisn
666 5468 United Reform Action URA
667 5486 Serbian People's Party SNS
668 5553 Front for a Country in Solidarity FREPASO
669 5650 Europe Ecology - The Greens EELV
670 5848 Simeon II Coalition KSII
671 5852 People’s Party FlF
672 5855 Forum for Democracy FvD
673 5856 DENK DENK
674 5857 Renaissance REM|R
675 5858 Indomitable France FI
676 5879 Democratic Center CD
677 5926 Green Alliance AV
678 5939 Portugal Ahead PàF
679 5969 Save Romania Union USR
680 5976 Social Democratic Party 'Harmony' SDPS
681 6042 Peoples' Democratic Party HDP
682 6087 Democratic Front DF
683 6110 National Regeneration Movement MORENA
684 6113 Social Encounter Party PES
685 6114 Alliance of Patriots of Georgia APG
686 6125 Freedom and Direct Democracy SPD
687 6131 The Left L
688 6135 Constitutional Democratic Party of Japan CDP
689 6150 Democratic Movement of Serbia DEPOS
690 6159 Civic Coalition CC
691 6560 Justice Party JP
692 6561 Democratic Party DPK
693 6648 Federal Peronism / Dissident Peronism PF-PJ
694 6691 Communist Party of Sri Lanka CP
695 6804 Lithuanian Christian Democratic Party LKDP
696 7049 Together for Change JxC
697 7339 The New Right NB
698 7348 European Realistic Disobedience Front MeRA25
699 7421 Together for Catalonia JxCat
700 7565 Centre Party M
701 7599 Blue and White KL
702 7619 National Alliance NA
703 7909 Movement for Change KINAL
704 7912 Joint List JL
705 8031 In Common We Can ECP
706 8054 Democrats of the Left DS
707 8058 Forza Italia FI
708 8168 The Republicans LR
709 8176 Geneva Citizens' Movement MCG
710 8182 Enough CH
711 8241 Solidarity–People Before Profit PBPS
712 8393 Development/For! AP!
713 8640 Liberal Iniciative IL
714 8842 Coalition of the Social Democratic Party of Croatia and the Croatian Social-liberal Party SDP-HSLS
715 9001 Ivica Dacic - Prime Minister of Serbia SPS/JS/ZS
716 9095 Rightwards Y
717 218 Polo Democratico Alternativo PDA
718 284 Unified Democratic Coalition CDU
719 568 Nationalist Revolutionary Movement MNR
720 751 SNK European Democrats SNK-ED
721 1036 Likud Likud
722 1060 Republican People's Party CHP
723 1308 Social Democratic Centre CDS-PP
724 1665 Coalition for Bulgaria BSP
725 1674 VMRO-DPMNE VMRO-DPMNE
726 1724 Radical Civic Union UCR
727 1985 Popular Socialist Party PPS
728 2234 Party of Regions PR
729 2256 United Russia ER
730 3677 Jewish Home HaBayit
731 4717 Democratic Front DF
732 7031 Left Ecology Freedom SEL
File diff suppressed because it is too large Load Diff
+2195
View File
File diff suppressed because it is too large Load Diff
+38203
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6864
View File
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
manifesto_pf_id,manifesto_name,expert_pf_id,expert_name,country,relationship,status,detection_method
3889,PJ,6648,PF-PJ,AR,Peronist faction no independent manifesto,implemented,manual
6161,FAP,1365,PS,AR,"LLM: PS (Socialist Party) was a core constituent of FAP (Frente Amplio Progresista), which published joint manifestos.",implemented,llm_verified
6161,FAP,6160,FR,AR,"LLM: FR (Frente Renovador) joined FAP in some elections, but also ran independently; likely constituent in FAP manifestos.",implemented,llm_verified
6161,FAP,6554,FPCyS,AR,"LLM: FPCyS (Frente Progresista, Cívico y Social) included PS and others; manifestos often under FAP or similar coalitions.",implemented,llm_verified
486,LPA,1998,LP,AU,"LLM: The Liberal Party (LP, PF ID: 1998) is the predecessor and constituent of the Liberal Party of Australia (LPA, PF ID: 486); manifestos are published under LPA, not LP.",implemented,llm_verified
1743,NPA,338,NAT,AU,"LLM: The National Party (NAT) is the predecessor and constituent of the National Party of Australia (NPA, PF ID: 1743), which is the name used for joint manifestos after the party's rebranding; manifestos are published under NPA, not NAT.",implemented,llm_verified
1760,HDZ BiH,3904,HDZ-HK~HNZ,BA,HDZ-led coalition variants,implemented,manual
36,N-VA,756,CD+NVA,BE,cartel list,implemented,manual
604,CD/V,622,CD&V,BE,same party duplicate PF ID,implemented,manual
604,CD/V,756,CD+NVA,BE,"LLM: CD+NVA refers to the joint electoral lists of CD&V and N-VA (mainly 2003-2007); during this period, they published joint manifestos under the CD/V (PF ID: 604) label in the text data.",implemented,llm_verified
1680,sp.a,1586,sp.a-SPIRIT,BE,merger,implemented,manual
374,NDSV,5848,KSII,BG,"LLM: KSII is the coalition led by NDSV (374); manifestos published under NDSV, not KSII.",implemented,llm_verified
482,SDS,3908,G-VMRO; VMRO-BND,BG,bloc constituent (progtype=8),implemented,progtype_8
1765,ONS,3908,G-VMRO; VMRO-BND,BG,bloc constituent (progtype=8),implemented,progtype_8
5649,Patriotic Front - NFSB and VMRO,2057,NFSB,BG,bloc constituent (progtype=8),implemented,progtype_8
5649,Patriotic Front - NFSB and VMRO,3908,G-VMRO; VMRO-BND,BG,bloc constituent (progtype=8),implemented,progtype_8
360,FDP/PLR,1231,FDP/PLR,CH,same party duplicate PF ID,implemented,manual
6061,Alliance,928,RN,CL,core coalition member joint CMP manifesto,implemented,manual
6061,Alliance,1599,UDI,CL,core coalition member joint CMP manifesto,implemented,manual
1707,STAN,751,SNK-ED,CZ,"LLM: SNK-ED ran joint lists and published manifestos together with STAN (PF ID: 1707) in parliamentary elections, notably in 2006, under the SNK-EDSTAN label.",implemented,llm_verified
2138,LB,1041,KSC,CZ,bloc constituent (progtype=8),implemented,progtype_8
6202,KDU-ČSL-US-DEU,104,US-DEU,CZ,bloc constituent (progtype=8),implemented,progtype_8
211,CDU/CSU,1375,CDU,DE,constituent,implemented,manual
211,CDU/CSU,1731,CSU,DE,constituent,implemented,manual
1816,B90/Grüne,10,Die Grünen,DE,merger,implemented,manual
3925,RED-ID,797,ID,EC,constituent of alliance,implemented,manual
685,RP,491,ERP,EE,LLM: ERP (Res Publica) published joint manifestos as 'RP' (PF ID: 685) in the text data; Res Publica is the Estonian name for the same party.,implemented,llm_verified
779,I/ERSP,908,RKI,EE,bloc constituent (progtype=8),implemented,progtype_8
779,I/ERSP,1299,ERSP,EE,merger,implemented,manual
139,CiU,4795,CDC,ES,constituent,implemented,manual
8271,CompromísPodemosEUPV,5623,CC,ES,LLM: CC (Compromís) published joint manifestos as part of CompromísPodemosEUPV (PF ID: 8271) in general elections.,implemented,llm_verified
213,MoDem,496,MoDem,FR,same party duplicate PF ID,implemented,manual
1108,EELV,5650,EELV,FR,same party duplicate PF ID,implemented,manual
1595,UMP,4628,Les Républicains,FR,UMP renamed 2015,implemented,manual
1595,UMP,8168,LR,FR,same as Les Républicains duplicate PF ID,implemented,manual
1468,PASOK,7909,KINAL,GR,PASOK-dominated umbrella rebranded 2022,implemented,manual
7347,EL,378,OP,GR,LLM: Oikologoi Prasinoi (OP) ran jointly with MeRA25 in 2019 as part of the 'MeRA25-Alliance for Breakup' and did not publish a separate manifesto; their program was subsumed under MeRA25.,implemented,llm_verified
1475,SDP,8842,SDP-HSLS,HR,joint list SDP dominant,implemented,manual
2522,Kukuriku,78,DC,HR,"LLM: DC (Democratic Centre) was a constituent of the Kukuriku coalition, which published joint manifestos under the Kukuriku name (PF ID: 2522) in 2011 and 2015.",implemented,llm_verified
3648,ZL,8036,HKDU,HR,bloc constituent (progtype=5),implemented,progtype_5
3918,DA-IDS-RDS,513,IDS,HR,constituent of alliance,implemented,manual
242,PBP,8241,PBPS,IE,LLM: PBPS (SolidarityPeople Before Profit) publishes joint manifestos under PBP (PF ID: 242) in the text data; this is a union.,implemented,llm_verified
201,UdC,1758,UC,IT,LLM: Unione di Centro (UC/UDC) is a constituent of UdC (PF ID: 201) in the text data; they publish joint manifestos under UdC.,implemented,llm_verified
1212,SEL,7031,SEL,IT,LLM: Sinistra Ecologia Libertà (SEL) is present in both datasets and publishes manifestos under SEL (PF ID: 1212) in text data.,implemented,llm_verified
1737,Olive Tree,878,DS,IT,bloc constituent (progtype=8),implemented,progtype_8
6241,House of Freedom,813,AN,IT,bloc constituent (progtype=8),implemented,progtype_8
6241,House of Freedom,1519,CeD,IT,bloc constituent (progtype=8),implemented,progtype_8
1967,SLFP,4020,CP / VLSSP,LK,"LLM: CP/VLSSP often contested as part of the SLFP-led United Front and People's Alliance, typically under joint manifestos with SLFP, but sometimes ran separately; medium confidence due to occasional independent runs.",implemented,llm_verified
1967,SLFP,5414,LSS,LK,"LLM: LSSP was a core constituent of the SLFP-led United Front and People's Alliance, publishing joint manifestos with SLFP.",implemented,llm_verified
1967,SLFP,6691,CP,LK,"LLM: CP was a constituent of the SLFP-led United Front and People's Alliance, publishing joint manifestos with SLFP.",implemented,llm_verified
197,BSDK,168,LRS,LT,bloc constituent (progtype=8),implemented,progtype_8
197,BSDK,1747,LMP-NDP,LT,bloc constituent (progtype=8),implemented,progtype_8
377,LTS,1410,LLaS,LT,bloc constituent (progtype=8),implemented,progtype_8
5779,SK,1407,LZP,LT,bloc constituent (progtype=8),implemented,progtype_8
186,LSAP/POSL,898,SDP,LU,same party duplicate PF ID,implemented,manual
708,LNNK-LZP,1296,LZP,LV,name fragment of LNNK-LZP,implemented,name_fragment
1704,TB-LNNK,671,TB,LV,constituent of alliance,implemented,manual
1704,TB-LNNK,1789,LNNK,LV,constituent of alliance,implemented,manual
1704,TB-LNNK,7619,NATBLNNK,LV,"LLM: NATBLNNK is a constituent of TB-LNNK (1704), which published joint manifestos as a union.",implemented,llm_verified
7622,ACUM,7904,PAS,MD,bloc constituent (progtype=8),implemented,progtype_8
3254,DSCG,3253,HGI,ME,LLM: HGI (Croatian Civic Initiative) is a small Croatian minority party that typically runs on joint lists with DSCG (Democratic Union of Croats) in Montenegro; manifestos are published under DSCG.,implemented,llm_verified
1537,GL,1533,Groen,NL,"LLM: Groen was a constituent party of GroenLinks (GL, PF ID: 1537), which published joint manifestos after its formation; Groen did not publish separate manifestos after joining the union.",implemented,llm_verified
716,Alliance,1119,NLP,NZ,LLM: NLP (NewLabour Party) was a founding constituent of the Alliance and published joint manifestos under the Alliance name.,implemented,llm_verified
4219,C90,5130,P2000,PE,"LLM: P2000 (Perú 2000) was a bloc including Cambio 90 (C90); manifestos were published under the C90/NM/P2000 bloc, which is represented as C90 (PF ID: 4219) in text data.",implemented,llm_verified
1458,WAK,70,ZChN,PL,bloc constituent (progtype=8),implemented,progtype_8
8268,UW,1566,D|W|U,PL,"LLM: D|W|U refers to Unia Wolności (UW) and its predecessors/successors, which published manifestos under the UW name (PF ID: 8268) in the text data.",implemented,llm_verified
192,CDR,645,PAC,RO,bloc constituent (progtype=8),implemented,progtype_8
1347,PSD-PUR,1443,PU|PC,RO,name fragment of PSD-PUR,implemented,name_fragment
5941,USL,120,PSD,RO,PSD was the lead constituent of USL coalition (2012 joint manifesto),implemented,manual
5941,USL,481,PNL,RO,PNL was a core constituent of USL coalition (2012 joint manifesto),implemented,manual
5941,USL,1541,UNPR,RO,LLM: UNPR was a constituent of the USL (PF ID: 5941) coalition and did not publish its own manifesto; manifestos were issued under the USL name.,implemented,llm_verified
6153,PSD-PC,1443,PU|PC,RO,name fragment of PSD-PC,implemented,name_fragment
8626,LDP/LSV/SDS,4769,LSV,RS,name fragment of LDP/LSV/SDS,implemented,name_fragment
205,SV,200,SDSS,SK,bloc constituent (progtype=8),implemented,progtype_8
226,SDK,200,SDSS,SK,bloc constituent (progtype=8),implemented,progtype_8
1617,SDKÚ-DS,983,DS,SK,DS merged into SDKÚ-DS,implemented,manual
6629,DÚS,707,DUS,SK,LLM: DUS (Demokratická únia Slovenska) is the same as DÚS (PF ID: 6629) in the manifesto data; manifestos are published under the union name.,implemented,llm_verified
1658,FA,3671,NE,UY,"LLM: NE (Nuevo Espacio) is a well-known constituent party of the Frente Amplio (FA) coalition, which publishes joint manifestos under the FA name.",implemented,llm_verified
301,"SYRIZA, SYN; SYRIZA, Syriza, SYN/SYRIZA",1682,DIKKI,GR,"LLM (bloc-centric): DIKKI was a constituent member of the SYRIZA bloc in the 2007 election, running under its banner and publishing joint manifestos.",implemented,llm_verified
676,"KDU, KDU-ČSL, KDU-CSL, KDU/CSL, KDUCSL, KDUCSL, KDU–Č, KDU- ČSL, CSL",824,KDS,CZ,LLM (bloc-centric): KDS (Christian Democratic Party) is explicitly listed as a constituent member of the 'Christian and Democratic Union - Czech People's Party' bloc in the PartyFacts comments and published joint manifestos with it.,implemented,llm_verified
701,"ZZS, LZS",1702,LZS,LV,"LLM (bloc-centric): Latvijas Zemnieku savienība (LZS) was a core constituent member of the Greens' and Farmers Union (ZZS) bloc in 2002, publishing joint manifestos and running under the bloc's banner.",implemented,llm_verified
852,"V, Unity, UNITY, VIENOTIBA, JV, PS",1531,JL,LV,LLM (bloc-centric): Jaunais laiks (JL) was a founding constituent of the Unity (Vienotība) bloc in 2010 and published joint manifestos under its banner.,implemented,llm_verified
2190,"DSS, DSS/NS",2346,NS,RS,"LLM (bloc-centric): New Serbia (NS) was a verified constituent member of the 'Democratic Party of Serbia/New Serbia' bloc in the 2008 elections, publishing joint manifestos with DSS.",implemented,llm_verified
2530,"FpV, FPV, FPV-PJ, AFplV, FplV",623,PJ,AR,"LLM (bloc-centric): The Justicialist Party (PJ) was the principal and founding constituent of the Front for Victory (FpV) bloc, running under its banner and publishing joint manifestos in all relevant elections.",implemented,llm_verified
3906,NA,356,PT,BR,"LLM (bloc-centric): PT (Partido dos Trabalhadores) was the leading party and consistent constituent of this left/progressive bloc across all listed elections, publishing joint manifestos under its banner.",implemented,llm_verified
3906,NA,723,PSB,BR,"LLM (bloc-centric): PSB (Partido Socialista Brasileiro) was a frequent coalition partner and constituent member of this bloc, including joint manifestos in several elections (notably 2002, 2006, 2010, 2014, and 2022).",implemented,llm_verified
3906,NA,1009,PDT,BR,"LLM (bloc-centric): PDT (Partido Democrático Trabalhista) was a constituent member of this bloc in multiple elections, including joint manifestos (notably 2010, 2018, and 2022).",implemented,llm_verified
3906,NA,4405,"PR, PR (2), PR / PL, PR/PL, PR PL, PL/PR",BR,"LLM (bloc-centric): PR (Partido da República) was a constituent member of the bloc in the 2010 and 2014 elections, publishing joint manifestos with the bloc.",implemented,llm_verified
3906,NA,458,PTB,BR,"LLM (bloc-centric): PTB (Partido Trabalhista Brasileiro) was a constituent member of the bloc in the 2002 and 2006 elections, participating in joint manifestos.",implemented,llm_verified
3906,NA,1823,PL,BR,"LLM (bloc-centric): PL (Partido Liberal) was a constituent member of the bloc in the 2002 election, publishing a joint manifesto.",implemented,llm_verified
4550,"C, Concertacion, CPD",6,PS,CL,"LLM (bloc-centric): The Socialist Party of Chile (PS) was a founding and continuous member of the Concertación/CPD, running on joint lists and publishing joint manifestos.",implemented,llm_verified
4550,"C, Concertacion, CPD",54,PPD,CL,"LLM (bloc-centric): The Party for Democracy (PPD) was a core constituent of the Concertación/CPD, participating in all its joint electoral platforms and manifestos.",implemented,llm_verified
4550,"C, Concertacion, CPD",390,PDC,CL,"LLM (bloc-centric): The Christian Democratic Party (PDC) was a principal founding member of the Concertación/CPD, running under its banner and signing joint manifestos.",implemented,llm_verified
4550,"C, Concertacion, CPD",437,PRSD,CL,"LLM (bloc-centric): The Radical Social Democratic Party (PRSD) was a constituent member of the Concertación/CPD, participating in joint electoral lists and manifestos.",implemented,llm_verified
8999,"ZMS, Aleksandar V..., PS-TN, Serbia is Wi..., ally",3177,SNS,RS,LLM (bloc-centric): The Serbian Progressive Party (SNS) was the leading and founding constituent of the 'Aleksandar Vučić Serbia wins / For Our Children / Serbia Must Not Stop' bloc in all listed elections and published joint manifestos under this banner.,implemented,llm_verified
1117,PO,4630,.N,PL,Nowoczesna was core constituent of Koalicja Obywatelska (KO) in 2019 under PO CMP code (progtype=8),implemented,manual
4550,Concertacion,162,PC,CL,Communist Party of Chile was constituent of Nueva Mayoría (2013-2017) under Concertación PF ID,implemented,manual
4550,Concertacion,209,PH,CL,Humanist Party was Concertación constituent (2005-2009),implemented,manual
5668,EH Bildu,1671,Amaiur,ES,Amaiur (2011) predecessor to EH Bildu; expert data 2014-2024 covers EH Bildu years,implemented,manual
6241,CdL,1767,CCD,IT,CdL coalition constituent (1994-2008),implemented,manual
3979,Salvemos a México,1474,PRI,MX,PRI-PVEM electoral coalition (2006-2012) under various names,implemented,manual
3979,Salvemos a México,446,PVEM,MX,PRI-PVEM electoral coalition (2006-2012) under various names,implemented,manual
7912,Joint List,421,Hadash,IL,Arab party coalition (2015-2021); Hadash is core constituent,implemented,manual
7912,Joint List,1663,Balad,IL,Arab party coalition (2015-2021); Balad is constituent,implemented,manual
365,PdL,1626,FI,IT,merger constituent,implemented,manual
365,PdL,813,AN,IT,merger constituent,implemented,manual
1 manifesto_pf_id manifesto_name expert_pf_id expert_name country relationship status detection_method
2 3889 PJ 6648 PF-PJ AR Peronist faction no independent manifesto implemented manual
3 6161 FAP 1365 PS AR LLM: PS (Socialist Party) was a core constituent of FAP (Frente Amplio Progresista), which published joint manifestos. implemented llm_verified
4 6161 FAP 6160 FR AR LLM: FR (Frente Renovador) joined FAP in some elections, but also ran independently; likely constituent in FAP manifestos. implemented llm_verified
5 6161 FAP 6554 FPCyS AR LLM: FPCyS (Frente Progresista, Cívico y Social) included PS and others; manifestos often under FAP or similar coalitions. implemented llm_verified
6 486 LPA 1998 LP AU LLM: The Liberal Party (LP, PF ID: 1998) is the predecessor and constituent of the Liberal Party of Australia (LPA, PF ID: 486); manifestos are published under LPA, not LP. implemented llm_verified
7 1743 NPA 338 NAT AU LLM: The National Party (NAT) is the predecessor and constituent of the National Party of Australia (NPA, PF ID: 1743), which is the name used for joint manifestos after the party's rebranding; manifestos are published under NPA, not NAT. implemented llm_verified
8 1760 HDZ BiH 3904 HDZ-HK~HNZ BA HDZ-led coalition variants implemented manual
9 36 N-VA 756 CD+NVA BE cartel list implemented manual
10 604 CD/V 622 CD&V BE same party duplicate PF ID implemented manual
11 604 CD/V 756 CD+NVA BE LLM: CD+NVA refers to the joint electoral lists of CD&V and N-VA (mainly 2003-2007); during this period, they published joint manifestos under the CD/V (PF ID: 604) label in the text data. implemented llm_verified
12 1680 sp.a 1586 sp.a-SPIRIT BE merger implemented manual
13 374 NDSV 5848 KSII BG LLM: KSII is the coalition led by NDSV (374); manifestos published under NDSV, not KSII. implemented llm_verified
14 482 SDS 3908 G-VMRO; VMRO-BND BG bloc constituent (progtype=8) implemented progtype_8
15 1765 ONS 3908 G-VMRO; VMRO-BND BG bloc constituent (progtype=8) implemented progtype_8
16 5649 Patriotic Front - NFSB and VMRO 2057 NFSB BG bloc constituent (progtype=8) implemented progtype_8
17 5649 Patriotic Front - NFSB and VMRO 3908 G-VMRO; VMRO-BND BG bloc constituent (progtype=8) implemented progtype_8
18 360 FDP/PLR 1231 FDP/PLR CH same party duplicate PF ID implemented manual
19 6061 Alliance 928 RN CL core coalition member joint CMP manifesto implemented manual
20 6061 Alliance 1599 UDI CL core coalition member joint CMP manifesto implemented manual
21 1707 STAN 751 SNK-ED CZ LLM: SNK-ED ran joint lists and published manifestos together with STAN (PF ID: 1707) in parliamentary elections, notably in 2006, under the SNK-ED–STAN label. implemented llm_verified
22 2138 LB 1041 KSC CZ bloc constituent (progtype=8) implemented progtype_8
23 6202 KDU-ČSL-US-DEU 104 US-DEU CZ bloc constituent (progtype=8) implemented progtype_8
24 211 CDU/CSU 1375 CDU DE constituent implemented manual
25 211 CDU/CSU 1731 CSU DE constituent implemented manual
26 1816 B90/Grüne 10 Die Grünen DE merger implemented manual
27 3925 RED-ID 797 ID EC constituent of alliance implemented manual
28 685 RP 491 ERP EE LLM: ERP (Res Publica) published joint manifestos as 'RP' (PF ID: 685) in the text data; Res Publica is the Estonian name for the same party. implemented llm_verified
29 779 I/ERSP 908 RKI EE bloc constituent (progtype=8) implemented progtype_8
30 779 I/ERSP 1299 ERSP EE merger implemented manual
31 139 CiU 4795 CDC ES constituent implemented manual
32 8271 Compromís–Podemos–EUPV 5623 CC ES LLM: CC (Compromís) published joint manifestos as part of Compromís–Podemos–EUPV (PF ID: 8271) in general elections. implemented llm_verified
33 213 MoDem 496 MoDem FR same party duplicate PF ID implemented manual
34 1108 EELV 5650 EELV FR same party duplicate PF ID implemented manual
35 1595 UMP 4628 Les Républicains FR UMP renamed 2015 implemented manual
36 1595 UMP 8168 LR FR same as Les Républicains duplicate PF ID implemented manual
37 1468 PASOK 7909 KINAL GR PASOK-dominated umbrella rebranded 2022 implemented manual
38 7347 EL 378 OP GR LLM: Oikologoi Prasinoi (OP) ran jointly with MeRA25 in 2019 as part of the 'MeRA25-Alliance for Breakup' and did not publish a separate manifesto; their program was subsumed under MeRA25. implemented llm_verified
39 1475 SDP 8842 SDP-HSLS HR joint list SDP dominant implemented manual
40 2522 Kukuriku 78 DC HR LLM: DC (Democratic Centre) was a constituent of the Kukuriku coalition, which published joint manifestos under the Kukuriku name (PF ID: 2522) in 2011 and 2015. implemented llm_verified
41 3648 ZL 8036 HKDU HR bloc constituent (progtype=5) implemented progtype_5
42 3918 DA-IDS-RDS 513 IDS HR constituent of alliance implemented manual
43 242 PBP 8241 PBPS IE LLM: PBPS (Solidarity–People Before Profit) publishes joint manifestos under PBP (PF ID: 242) in the text data; this is a union. implemented llm_verified
44 201 UdC 1758 UC IT LLM: Unione di Centro (UC/UDC) is a constituent of UdC (PF ID: 201) in the text data; they publish joint manifestos under UdC. implemented llm_verified
45 1212 SEL 7031 SEL IT LLM: Sinistra Ecologia Libertà (SEL) is present in both datasets and publishes manifestos under SEL (PF ID: 1212) in text data. implemented llm_verified
46 1737 Olive Tree 878 DS IT bloc constituent (progtype=8) implemented progtype_8
47 6241 House of Freedom 813 AN IT bloc constituent (progtype=8) implemented progtype_8
48 6241 House of Freedom 1519 CeD IT bloc constituent (progtype=8) implemented progtype_8
49 1967 SLFP 4020 CP / VLSSP LK LLM: CP/VLSSP often contested as part of the SLFP-led United Front and People's Alliance, typically under joint manifestos with SLFP, but sometimes ran separately; medium confidence due to occasional independent runs. implemented llm_verified
50 1967 SLFP 5414 LSS LK LLM: LSSP was a core constituent of the SLFP-led United Front and People's Alliance, publishing joint manifestos with SLFP. implemented llm_verified
51 1967 SLFP 6691 CP LK LLM: CP was a constituent of the SLFP-led United Front and People's Alliance, publishing joint manifestos with SLFP. implemented llm_verified
52 197 BSDK 168 LRS LT bloc constituent (progtype=8) implemented progtype_8
53 197 BSDK 1747 LMP-NDP LT bloc constituent (progtype=8) implemented progtype_8
54 377 LTS 1410 LLaS LT bloc constituent (progtype=8) implemented progtype_8
55 5779 SK 1407 LZP LT bloc constituent (progtype=8) implemented progtype_8
56 186 LSAP/POSL 898 SDP LU same party duplicate PF ID implemented manual
57 708 LNNK-LZP 1296 LZP LV name fragment of LNNK-LZP implemented name_fragment
58 1704 TB-LNNK 671 TB LV constituent of alliance implemented manual
59 1704 TB-LNNK 1789 LNNK LV constituent of alliance implemented manual
60 1704 TB-LNNK 7619 NATBLNNK LV LLM: NATBLNNK is a constituent of TB-LNNK (1704), which published joint manifestos as a union. implemented llm_verified
61 7622 ACUM 7904 PAS MD bloc constituent (progtype=8) implemented progtype_8
62 3254 DSCG 3253 HGI ME LLM: HGI (Croatian Civic Initiative) is a small Croatian minority party that typically runs on joint lists with DSCG (Democratic Union of Croats) in Montenegro; manifestos are published under DSCG. implemented llm_verified
63 1537 GL 1533 Groen NL LLM: Groen was a constituent party of GroenLinks (GL, PF ID: 1537), which published joint manifestos after its formation; Groen did not publish separate manifestos after joining the union. implemented llm_verified
64 716 Alliance 1119 NLP NZ LLM: NLP (NewLabour Party) was a founding constituent of the Alliance and published joint manifestos under the Alliance name. implemented llm_verified
65 4219 C90 5130 P2000 PE LLM: P2000 (Perú 2000) was a bloc including Cambio 90 (C90); manifestos were published under the C90/NM/P2000 bloc, which is represented as C90 (PF ID: 4219) in text data. implemented llm_verified
66 1458 WAK 70 ZChN PL bloc constituent (progtype=8) implemented progtype_8
67 8268 UW 1566 D|W|U PL LLM: D|W|U refers to Unia Wolności (UW) and its predecessors/successors, which published manifestos under the UW name (PF ID: 8268) in the text data. implemented llm_verified
68 192 CDR 645 PAC RO bloc constituent (progtype=8) implemented progtype_8
69 1347 PSD-PUR 1443 PU|PC RO name fragment of PSD-PUR implemented name_fragment
70 5941 USL 120 PSD RO PSD was the lead constituent of USL coalition (2012 joint manifesto) implemented manual
71 5941 USL 481 PNL RO PNL was a core constituent of USL coalition (2012 joint manifesto) implemented manual
72 5941 USL 1541 UNPR RO LLM: UNPR was a constituent of the USL (PF ID: 5941) coalition and did not publish its own manifesto; manifestos were issued under the USL name. implemented llm_verified
73 6153 PSD-PC 1443 PU|PC RO name fragment of PSD-PC implemented name_fragment
74 8626 LDP/LSV/SDS 4769 LSV RS name fragment of LDP/LSV/SDS implemented name_fragment
75 205 SV 200 SDSS SK bloc constituent (progtype=8) implemented progtype_8
76 226 SDK 200 SDSS SK bloc constituent (progtype=8) implemented progtype_8
77 1617 SDKÚ-DS 983 DS SK DS merged into SDKÚ-DS implemented manual
78 6629 DÚS 707 DUS SK LLM: DUS (Demokratická únia Slovenska) is the same as DÚS (PF ID: 6629) in the manifesto data; manifestos are published under the union name. implemented llm_verified
79 1658 FA 3671 NE UY LLM: NE (Nuevo Espacio) is a well-known constituent party of the Frente Amplio (FA) coalition, which publishes joint manifestos under the FA name. implemented llm_verified
80 301 SYRIZA, SYN; SYRIZA, Syriza, SYN/SYRIZA 1682 DIKKI GR LLM (bloc-centric): DIKKI was a constituent member of the SYRIZA bloc in the 2007 election, running under its banner and publishing joint manifestos. implemented llm_verified
81 676 KDU, KDU-ČSL, KDU-CSL, KDU/CSL, KDUCSL, KDU–CSL, KDU–Č, KDU- ČSL, CSL 824 KDS CZ LLM (bloc-centric): KDS (Christian Democratic Party) is explicitly listed as a constituent member of the 'Christian and Democratic Union - Czech People's Party' bloc in the PartyFacts comments and published joint manifestos with it. implemented llm_verified
82 701 ZZS, LZS 1702 LZS LV LLM (bloc-centric): Latvijas Zemnieku savienība (LZS) was a core constituent member of the Greens' and Farmers’ Union (ZZS) bloc in 2002, publishing joint manifestos and running under the bloc's banner. implemented llm_verified
83 852 V, Unity, UNITY, VIENOTIBA, JV, PS 1531 JL LV LLM (bloc-centric): Jaunais laiks (JL) was a founding constituent of the Unity (Vienotība) bloc in 2010 and published joint manifestos under its banner. implemented llm_verified
84 2190 DSS, DSS/NS 2346 NS RS LLM (bloc-centric): New Serbia (NS) was a verified constituent member of the 'Democratic Party of Serbia/New Serbia' bloc in the 2008 elections, publishing joint manifestos with DSS. implemented llm_verified
85 2530 FpV, FPV, FPV-PJ, AFplV, FplV 623 PJ AR LLM (bloc-centric): The Justicialist Party (PJ) was the principal and founding constituent of the Front for Victory (FpV) bloc, running under its banner and publishing joint manifestos in all relevant elections. implemented llm_verified
86 3906 NA 356 PT BR LLM (bloc-centric): PT (Partido dos Trabalhadores) was the leading party and consistent constituent of this left/progressive bloc across all listed elections, publishing joint manifestos under its banner. implemented llm_verified
87 3906 NA 723 PSB BR LLM (bloc-centric): PSB (Partido Socialista Brasileiro) was a frequent coalition partner and constituent member of this bloc, including joint manifestos in several elections (notably 2002, 2006, 2010, 2014, and 2022). implemented llm_verified
88 3906 NA 1009 PDT BR LLM (bloc-centric): PDT (Partido Democrático Trabalhista) was a constituent member of this bloc in multiple elections, including joint manifestos (notably 2010, 2018, and 2022). implemented llm_verified
89 3906 NA 4405 PR, PR (2), PR / PL, PR/PL, PR PL, PL/PR BR LLM (bloc-centric): PR (Partido da República) was a constituent member of the bloc in the 2010 and 2014 elections, publishing joint manifestos with the bloc. implemented llm_verified
90 3906 NA 458 PTB BR LLM (bloc-centric): PTB (Partido Trabalhista Brasileiro) was a constituent member of the bloc in the 2002 and 2006 elections, participating in joint manifestos. implemented llm_verified
91 3906 NA 1823 PL BR LLM (bloc-centric): PL (Partido Liberal) was a constituent member of the bloc in the 2002 election, publishing a joint manifesto. implemented llm_verified
92 4550 C, Concertacion, CPD 6 PS CL LLM (bloc-centric): The Socialist Party of Chile (PS) was a founding and continuous member of the Concertación/CPD, running on joint lists and publishing joint manifestos. implemented llm_verified
93 4550 C, Concertacion, CPD 54 PPD CL LLM (bloc-centric): The Party for Democracy (PPD) was a core constituent of the Concertación/CPD, participating in all its joint electoral platforms and manifestos. implemented llm_verified
94 4550 C, Concertacion, CPD 390 PDC CL LLM (bloc-centric): The Christian Democratic Party (PDC) was a principal founding member of the Concertación/CPD, running under its banner and signing joint manifestos. implemented llm_verified
95 4550 C, Concertacion, CPD 437 PRSD CL LLM (bloc-centric): The Radical Social Democratic Party (PRSD) was a constituent member of the Concertación/CPD, participating in joint electoral lists and manifestos. implemented llm_verified
96 8999 ZMS, Aleksandar V..., PS-TN, Serbia is Wi..., ally 3177 SNS RS LLM (bloc-centric): The Serbian Progressive Party (SNS) was the leading and founding constituent of the 'Aleksandar Vučić – Serbia wins / For Our Children / Serbia Must Not Stop' bloc in all listed elections and published joint manifestos under this banner. implemented llm_verified
97 1117 PO 4630 .N PL Nowoczesna was core constituent of Koalicja Obywatelska (KO) in 2019 under PO CMP code (progtype=8) implemented manual
98 4550 Concertacion 162 PC CL Communist Party of Chile was constituent of Nueva Mayoría (2013-2017) under Concertación PF ID implemented manual
99 4550 Concertacion 209 PH CL Humanist Party was Concertación constituent (2005-2009) implemented manual
100 5668 EH Bildu 1671 Amaiur ES Amaiur (2011) predecessor to EH Bildu; expert data 2014-2024 covers EH Bildu years implemented manual
101 6241 CdL 1767 CCD IT CdL coalition constituent (1994-2008) implemented manual
102 3979 Salvemos a México 1474 PRI MX PRI-PVEM electoral coalition (2006-2012) under various names implemented manual
103 3979 Salvemos a México 446 PVEM MX PRI-PVEM electoral coalition (2006-2012) under various names implemented manual
104 7912 Joint List 421 Hadash IL Arab party coalition (2015-2021); Hadash is core constituent implemented manual
105 7912 Joint List 1663 Balad IL Arab party coalition (2015-2021); Balad is constituent implemented manual
106 365 PdL 1626 FI IT merger constituent implemented manual
107 365 PdL 813 AN IT merger constituent implemented manual
+240
View File
@@ -0,0 +1,240 @@
# Data Coding Principles for 4D Latent Trait Model
## V4 Implementation (Current Version)
**As of V4 (2025-11-18), manifesto items implement the bipolar bridge structure described in this document.**
**Key Changes from V3.x**:
- ✅ Manifesto items now load on TWO dimensions (bipolar bridges)
- ✅ Data format: `type_high` and `type_low` columns replace single `type`
- ✅ Stan model: Unified `Gamma_man` matrix replaces per-dimension arrays
- ✅ Measurement consistency: Manifesto items match expert data structure
**Why V4?**
- Better identification (each observation informs two dimensions)
- Estimated correlations (not imposed by construction)
- No double-counting (each quasi-sentence counted once)
See CHANGELOG.md for full V4 migration details.
---
## Model Structure Overview
The model estimates **four unipolar latent dimensions**:
- **pro_market**: Support for market liberalization
- **pro_welfare**: Support for welfare state expansion
- **cosmopolitan**: Support for internationalism, diversity, openness
- **traditional**: Support for nationalism, security, traditional values
These are **separate dimensions**, not two bipolar scales. Correlations between dimensions (e.g., cosmopolitan-traditional) are **estimated empirically**, not imposed by construction.
---
## Item Types and Loading Structure
### 1. Bipolar Bridge Items
**Definition**: Items where the sample includes mentions of BOTH sides of an issue, and "positive" counts mentions favoring one pole.
**Structure**:
- `sample` = mentions of issue (any direction)
- `positive` = mentions favoring one pole
- `positive/sample` ratio varies from 0 to 1
**Loading**: Should load on **ONE dimension only**
**Examples**:
**Manifesto Data**:
```
var: "Multiculturalism"
type: "cosmopolitan"
sample: per607 (pro-multiculturalism) + per608 (anti-multiculturalism)
positive: per607 (pro-multiculturalism)
```
- High ratio → high cosmopolitan (party favors multiculturalism)
- Low ratio → low cosmopolitan (party opposes multiculturalism)
- Anti-multiculturalism is **implicitly measured** as (sample - positive)
**PolDem Data**:
```
var: "Immigration (Media)"
type: "cosmopolitan"
sample: all immigration mentions (direction != 0)
positive: pro-immigration mentions (direction > 0)
```
- High ratio → high cosmopolitan (media coverage shows party supporting immigration)
- Low ratio → low cosmopolitan (media coverage shows party opposing immigration)
### 2. Why One Loading Suffices for Bipolar Items
**Question**: Shouldn't anti-immigration also load on traditional?
**Answer**: No, because:
1. **Both poles are already captured**: The bipolar structure means low cosmopolitan (anti-immigration) is automatically measured
2. **Avoids double-counting**: Each mention/quasi-sentence contributes to exactly ONE item
3. **Empirical correlations emerge naturally**: If anti-immigration parties also score high on nationalism/law-and-order, the **posterior correlation** between cosmopolitan and traditional will reflect this
4. **More flexible model**: Cosmopolitan-traditional relationship is **estimated**, not imposed
**Imposed vs. Estimated Correlation**:
- If we double-load immigration on both cosmopolitan (negative) and traditional (positive), we **force** them to be opposites
- By loading only on cosmopolitan, we let the data reveal whether anti-immigration parties are also nationalist (empirical question)
---
## Coding Decision Rules
### Rule 1: Each Manifesto Code Appears in ONE Item Only
**Good** (current structure):
```
"Multiculturalism" (cosmopolitan):
- per607 (Positive), per608 (Negative)
"National Identity" (traditional):
- per601 (Positive), per107 (Negative)
```
- per607/per608 only in cosmopolitan
- per601/per107 only in traditional
- Correlation between dimensions is empirical
**Bad** (double-loading):
```
"Multiculturalism" (cosmopolitan):
- per607 (Positive), per601 (Negative)
"National Identity" (traditional):
- per601 (Positive), per607 (Negative)
```
- per601 and per607 counted twice
- Imposes perfect negative correlation between cosmopolitan/traditional
### Rule 2: Stance Assignment Within Items
Within each item (var), codes are assigned stance based on:
- **Positive**: Codes indicating support for the item's construct
- **Negative**: Codes indicating opposition to the item's construct
**Example - "Internationalism" (cosmopolitan)**:
- per107 (Internationalism positive): stance = "Positive"
- per109 (Internationalism negative): stance = "Negative"
- Result: High per107 / low per109 → high cosmopolitan score
### Rule 3: PolDem Direction Mapping
PolDem uses `direction` variable (-1, 0, +1):
- `direction > 0`: Support for the issue as coded
- `direction < 0`: Opposition to the issue
- `direction == 0`: Ambivalent (exclude from analysis)
**Aggregation**:
```r
poldem_processed %>%
filter(direction != 0) %>% # exclude neutral
group_by(party, year, country, issue_cat) %>%
summarise(
sample = n(), # all non-neutral mentions
positive = sum(direction > 0) # supportive mentions only
)
```
---
## Special Cases
### Immigration (Direction Ambiguity)
**Codebook says**: "Opposition to restrictive immigration"
**Interpretation needed**: Does `direction = +1` mean:
- A) Support for "opposition to restrictions" → pro-immigration (cosmopolitan)
- B) Support for "restrictions" → anti-immigration (traditional)
**Resolution**: Must manually inspect sample sentences before finalizing coding.
If interpretation A is correct:
```r
issue_cat == "immig" & direction > 0positive for cosmopolitan
issue_cat == "immig" & direction < 0negative for cosmopolitan
```
If interpretation B is correct:
```r
issue_cat == "immig" & direction > 0negative for cosmopolitan
issue_cat == "immig" & direction < 0positive for cosmopolitan
# (REVERSED)
```
### Europe/Euro Items
EU integration naturally maps to cosmopolitan-traditional dimension:
**Manifesto Data**:
- Add new items using per108 (EU integration positive) and per106 (EU integration negative)
- Create separate vars: "EU Integration Support" (cosmopolitan), "Euroskepticism" (traditional)
**PolDem Data**:
```r
"EU Integration Support (Media)" (cosmopolitan):
issue_cat = "europe" or "euro"
sample = all mentions
positive = direction > 0 (pro-EU)
"Euroskepticism (Media)" (traditional):
issue_cat = "europe" or "euro"
sample = all mentions
positive = direction < 0 (anti-EU)
```
**Note**: Same sentences contribute to BOTH items, but counting opposite directions. This creates natural negative correlation between cosmopolitan/traditional.
**Alternative approach** (cleaner, recommended): Load only on cosmopolitan:
```r
"EU Position (Media)" (cosmopolitan):
issue_cat = "europe" or "euro"
sample = all mentions
positive = direction > 0
```
This is sufficient if we treat EU as a bipolar cosmopolitan item.
---
## Data Structure Requirements
### Manifesto Data Format (party-year-var level)
Each row represents one item for one party-year:
| party | country | year | var | type | sample | positive | project |
|-------|---------|------|-----|------|--------|----------|---------|
| 211 | DE | 2013 | Multiculturalism | cosmopolitan | 45 | 23 | Manifesto |
| 211 | DE | 2013 | National Identity | traditional | 67 | 58 | Manifesto |
| 211 | DE | 2013 | Economic Intervention | pro_welfare | 102 | 78 | Manifesto |
- **var**: Item name (e.g., "Multiculturalism", "Economic Intervention")
- **type**: Dimension it loads on (pro_market, pro_welfare, cosmopolitan, traditional)
- **sample**: Total quasi-sentences mentioning this issue
- **positive**: Quasi-sentences with positive stance toward this item
### PolDem Data Format (same structure)
| party | country | year | var | type | sample | positive | project |
|-------|---------|------|-----|------|--------|----------|---------|
| 211 | DE | 2013 | Immigration (Media) | cosmopolitan | 23 | 8 | PolDem |
| 211 | DE | 2013 | Nationalism (Media) | traditional | 15 | 12 | PolDem |
Combined using `bind_rows()` to create unified dataset.
---
## Summary
1. **Bipolar items load on one dimension only** - the ratio captures both poles
2. **Each manifesto code appears in exactly one item** - no double-counting
3. **Correlations between dimensions are estimated, not imposed** - more flexible model
4. **Direction reversals are handled within items** - via stance assignment (Manifesto) or direction sign (PolDem)
5. **All items must allow varying positive/sample ratios** - mix of positive and negative stances required
This structure preserves the conceptual independence of the four dimensions while allowing the data to reveal their empirical relationships.
+73
View File
@@ -0,0 +1,73 @@
# Full model run operations
This note describes how to launch, monitor, and summarize the long full model
run that generates the posterior estimates.
Do not start a full run until raw-data and Stan-data preflight checks pass.
## Hardware note for runtime reporting
The production run should record elapsed time for this workstation as:
```text
Hardware: 4 cores of an AMD Ryzen 9 7945HX
```
After the run, record the wall-time reported by `src/sh/show_run_progress.sh` or
`outputs/model_outputs/latest/run_*/diagnostics/run_metrics.json`.
## Launch with durable logging
From the repository root:
```bash
mkdir -p outputs/logs
ts="$(date +%Y%m%d_%H%M%S)"
STAN_REFRESH=100 \
nohup bash run_estimation.sh full \
> "outputs/logs/full_run_${ts}.log" 2>&1 &
echo $! > "outputs/logs/full_run_${ts}.pid"
```
The wrapper configures local raw data, local R libraries, project-local temp
space, and the writable project-local CmdStan copy by default.
## Monitor during the run
Live log tail:
```bash
tail -f outputs/logs/full_run_<timestamp>.log
```
Convenience progress command:
```bash
bash src/sh/show_run_progress.sh
```
Stan progress is printed every `STAN_REFRESH` iterations (default `100`) and is
captured in the durable log.
## Inspect after completion
After a successful run, the chain CSVs are under:
```text
outputs/model_outputs/latest/run_<timestamp>/chains/
```
Run diagnostics and Stan logs are archived under:
```text
outputs/model_outputs/latest/run_<timestamp>/diagnostics/
```
Use:
```bash
bash src/sh/show_run_progress.sh
```
to print the latest run status, wall time, sampler configuration, divergences,
tree-depth hits, and command-configuration verification status.
+67
View File
@@ -0,0 +1,67 @@
# Raw data sources and local-only setup
The public replication repository includes processed inputs under `data/`, but it
does not redistribute licensed or third-party raw source files. Raw files needed
to regenerate processed inputs should be kept in a local-only directory outside
the replication git repository.
Recommended local layout:
```text
_local/raw/
poldem/poldem-election_all.csv
manifesto/MPDataset_MPDS2025a.csv
```
The scripts read `PARTY2D_RAW_DATA_DIR` when it is set. For another location, use:
```bash
export PARTY2D_RAW_DATA_DIR=/path/to/local/raw
```
## Required raw inputs
| Source | Raw file | Local path below `$PARTY2D_RAW_DATA_DIR` | Used by | Redistribution |
| --- | --- | --- | --- | --- |
| PolDem Election Campaigns, all issues | `poldem-election_all.csv` | `poldem/poldem-election_all.csv` | `src/r/00c_process_poldem.R` | Not redistributed in this repo |
| Manifesto Project Dataset | `MPDataset_MPDS2025a.csv` | `manifesto/MPDataset_MPDS2025a.csv` | `src/r/00a_process_manifesto.R` if `data/manifesto_data.csv` is regenerated | Not redistributed in this repo |
## PolDem download
Dataset: `poldem-election_all` from the PolDem Election Campaigns collection.
- Overview: <https://poldem.eui.eu/data-overview/>
- Download page: <https://poldem.eui.eu/download/election-campaigns/>
- CSV URL used locally on 2026-06-11:
<https://poldem.eui.eu/downloads/cosa/poldem-election_all.csv>
Observed checksum after download on 2026-06-11:
```text
sha256 2cd8c9108b1b0b9c1b6594bb21acee709c70259cd02f450bc69fc09b505fc9fb
```
## Preflight check
From the repository root, run:
```bash
bash src/sh/check_raw_data.sh
```
This checks required local raw inputs and prints byte sizes and SHA-256 checksums.
## Processed files kept in this repository
The processed files under `data/` are intended to be part of this repository,
including:
- `data/poldem_data.csv`
- `data/manifesto_data.csv`
- `data/text_data.csv`
- `data/expert.csv`
- `data/lr_data.csv`
- `data/model_data.csv`
These files document the analysis-ready inputs while avoiding redistribution of
the underlying raw source data.
+357
View File
@@ -0,0 +1,357 @@
# Party Union Mapping
## Individual Party Guarantee
**Every `party_id` in the output CSV represents an individual political party, never an electoral alliance or bloc.**
Electoral alliances and blocs are handled in one of two ways:
1. **Decomposed via mean-constituent averaging** (N=123 mappings): Shared manifesto data feeds into individual constituent party estimates. The output contains the constituents, not the alliance.
2. **Excluded with documented justification** (see "Excluded Alliance Labels" below): Alliance labels with no mappable constituents are dropped from the output.
A systematic audit of all output parties is provided by `scripts/audit_party_types.py`, which produces `scripts/party_type_audit.csv` classifying every party with evidence. Post-estimation verification in `02_post_estimation.jl` hard-fails if any union/alliance PF ID appears in the output.
## Overview
Expert surveys often rate individual constituent parties separately, while manifesto data is published under a union/coalition/merged party name. This document maps individual party PartyFacts IDs to the union PartyFacts IDs used in `text_data.csv`.
**Direction:** Individual party (expert data) → Union (manifesto/text data)
## Model Integration (V4 Mean-Constituent Model)
The V4 Stan model (`stan_model_2dim_v4.stan`) uses these mappings to produce **individual party estimates** for all constituent parties. Instead of collapsing expert data onto the union ID, V4 gives every constituent its own latent position (theta) and links shared manifesto data to the **mean** of constituent thetas.
### How it works
1. **Each constituent gets its own segment and random walk.** CDU (1375) and CSU (1731) each have independent theta trajectories. The union ID (211) gets NO segment.
2. **Manifesto observations constrain the mean.** A CDU/CSU manifesto in year t enters the likelihood as:
```
pos = mean(theta[dim, rr_CDU_t], theta[dim, rr_CSU_t])
```
The observation is counted once (no double-counting), but it pulls both constituents' thetas via their average.
3. **Expert data constrains individuals directly.** A CHES rating of CDU in 2019 maps to `theta[dim, rr_CDU_2019]` with no averaging. This is what identifies the *difference* between constituents.
4. **Identification depends on data availability:**
- **Periods with individual expert data** (e.g., CHES 1999-2024): Constituent estimates separate meaningfully. CSU appears more conservative on galtan than CDU, matching known ground truth.
- **Periods without individual expert data** (e.g., 1950s-1990s): Only the shared manifesto constrains the mean. The random walk prior pulls constituents toward similar values, so CDU ≈ CSU with wide credible intervals on the gap. The estimates gradually differentiate as expert data appears.
5. **Backwards compatible.** With an empty `union_mapping.csv`, all observations have `n_const=1` and V4 reduces exactly to V3.
### Data flow
```
R pipeline (00_data-management.R):
- text_data keeps union PF IDs (211) for manifesto rows
- expert/lr_data keeps individual PF IDs (1375, 1731)
- Expert filtering: party in text_data OR party in constituent_parties
Julia pipeline:
02_data_loading.jl → loads union_mapping.csv → builds union_to_constituents dict
03_data_preparation.jl → creates segments for CONSTITUENTS (not unions)
→ builds flat arrays: n_const_man[], const_offset_man[], const_rr_man[]
04_model_execution.jl → passes constituent arrays to Stan
Stan model (stan_model_2dim_v4.stan):
- Manifesto likelihood: averages theta over constituents per observation
- Expert likelihoods: same averaging (nc=1 for individual obs = direct lookup)
- No new parameters vs V3 — only data block and likelihood computation change
```
### Output
Post-estimation produces individual party rows (CDU=1375 and CSU=1731 as separate rows) with a `union_party_id` column (= 211 for both, NA for standalone parties). The anchor party is CDU (1375) instead of CDU/CSU (211).
### Scale impact (smoke test, post-1990 data)
- 471 parties → 483 segments, 461 with valid segments (vs ~449 without unions)
- 708 union manifesto observations with multi-constituent averaging (3.2% of 21,991 total)
- 23,427 total constituent entries in flat arrays
- 9,870 segment-year positions (R)
- No new model parameters (theta simply covers more segment-years)
- Negligible performance impact: `nc=1` fast path for >96% of observations
### Data quality (verified 2026-02-08)
- **No double-counting**: No constituents appear in text_data alongside their union
- **Union IDs verified absent from output** via post-estimation check in `02_post_estimation.jl`
- **No chain mappings**: No PF ID serves as both union target and constituent of another union
- **No duplicate rows** in text_data after deduplication
- **All flagged-for-review parties** in audit script are confirmed individual parties (false positives from name patterns)
## Detection Methodology
`scripts/diagnose_party_mismatches.R` uses a multi-signal approach, ranked by reliability:
### Phase 1: MARPOR `progtype` variable (definitive)
The raw Manifesto Project data (`MPDataset_MPDS2025a.csv`) contains a `progtype` variable classifying each manifesto entry:
| progtype | Meaning | Relevance |
|----------|---------|-----------|
| 1 | Party's own manifesto | Individual party, no union issue |
| **2** | **Programme of 2+ parties (individual tracking)** | Joint manifesto: each constituent gets its own CMP code with identical scores |
| 3 | Electoral manifesto by a single party | Individual party |
| **4** | **Estimated from another party's programme** | Party had no manifesto, inherited another party's scores |
| **5** | **Average of member parties' manifestos** | MARPOR computed average from constituent parties |
| 6 | Other | Miscellaneous |
| **8** | **Party bloc programme** | Bloc-level CMP code representing multiple parties |
| 9 | Non-standard text | Various sources |
**Phase 1a (progtype=2):** Group by `(country, date)` where progtype=2 to find which CMP codes shared a joint manifesto at each election. Map each to PF IDs. If some are in text_data and others are expert-only, create constituent mappings.
**Phase 1b (progtype=5):** Average-of-members entries have their own CMP code. Find constituents via PartyFacts "composed of:" comments and manifesto party names.
**Phase 1c (progtype=8):** Bloc-level entries have their own CMP code. Find constituents via PartyFacts "composed of:" comments and manifesto party names.
**Phase 1d (progtype=4):** Proxy entries where MARPOR estimated from another party. Currently detects 0 expert-only parties with progtype=4.
### Phase 2: PartyFacts metadata enrichment
**Phase 2a (comment parsing):** Search text_data party comments for "composed of:" patterns. Match mentioned abbreviations against expert-only parties using word-boundary matching.
**Phase 2b (parlgov "+" notation):** parlgov uses "+" to denote unions (e.g., "CDU+CSU", "CCD+CDU"). Parse fragments and match against expert-only parties.
**Phase 2c (name fragment matching):** For text_data parties with "/" or "-" in names, split into fragments and match against expert-only parties. **Bilingual disambiguation:** if all fragments resolve to the same PF ID, the "/" separates language variants (e.g., Swiss SPS/PSS), not constituents.
### Phase 3: LLM verification (optional)
For remaining unmatched expert-only parties, uses gpt-4.1 via GESIS OpenWebUI with enriched prompts containing: all name_short values across datasets, year ranges, PartyFacts comments, progtype history, and the full list of text_data parties in the same country. Skip with `--skip-llm` flag.
### Matching safeguards
- **Word-boundary matching:** Uses `\b` regex boundaries to prevent substring false positives (e.g., "DS" matching "NDSI")
- **Minimum 2-character terms:** Single-letter abbreviations (e.g., "K", "G") are excluded from matching
- **Deduplication:** When the same mapping is detected by multiple methods, the highest-reliability method is kept (manual > progtype > comments > parlgov > name_fragment > LLM)
- **Idempotent:** The script strips `detection_method` from existing mappings on re-run
## How This Was Built
1. **Original 36 mappings** (2026-02-06/07): String matching + LLM + RA verification + independent research
2. **Expanded with progtype** (2026-02-07): Rewrote `scripts/diagnose_party_mismatches.R` to use MARPOR progtype (types 2, 4, 5, 8), PartyFacts comments, parlgov "+", and bilingual-aware name matching
3. **LLM verification with gpt-4.1** (2026-02-07): Ran enriched prompts through gpt-4.1 via GESIS OpenWebUI for 345 remaining expert-only parties. Found 32 additional verified constituent mappings across 25 countries.
4. **Bloc-centric LLM sweep** (2026-02-07): For each of 38 unmapped progtype=8 bloc parties in text_data, sent targeted gpt-4.1 queries asking which expert-only parties are actual constituents. Found 17 new mappings (Brazil PT-led coalitions, Chilean Concertación, Serbian blocs, Latvian unions, etc.).
5. **Manual research** (2026-02-07): Verified remaining unmapped blocs. Added 4 manual mappings (PL: .N→KO; CL: PC→Concertación, PH→Concertación; ES: Amaiur→EH Bildu). Confirmed remaining 27 blocs have no mappable expert-only constituents (constituents either already in text_data, not in expert surveys, or in fluid coalitions).
6. **Temporary election coalitions** (2026-02-08): Systematic analysis of progtype=2 joint manifesto groups. Grouped by identical CMP content (all 142 per* columns) to separate left and right coalitions within the same election. Found 9 unmapped expert-only parties that shared manifestos with text_data parties (Italy 2001/2006/2013, France 2017). Added CCD→CdL from orphan analysis (1 manual). Total: 10 new mappings.
7. **Individual party guarantee cleanup** (2026-02-08): Removed 11 progtype_2/progtype_2_joint entries that mapped real individual parties as unions of zero-data coalition partners. Removed 2 chain mappings (union→union→constituent). Added 4 new mappings for genuine alliances (MX: Salvemos a México→{PRI, PVEM}; IL: Joint List→{Hadash, Balad}). Documented classification decisions for all 15 dual-progtype parties and 4 pure bloc labels. Added post-estimation verification check and audit script.
## Mapping Table
The canonical mapping is in `data/union_mapping.csv` with columns:
- `manifesto_pf_id` — PartyFacts ID of union (in text_data)
- `manifesto_name` — Union display name
- `expert_pf_id` — PartyFacts ID of individual constituent (in expert_raw)
- `expert_name` — Individual party display name
- `country` — ISO2 country code
- `relationship` — Description of the relationship
- `status` — `implemented` (active in pipeline) or `pending` (awaiting implementation)
- `detection_method` — How detected: `manual`, `progtype_2`, `progtype_2_joint`, `progtype_5`, `progtype_8`, `progtype_4`, `composed_comment`, `parlgov_plus`, `name_fragment`, `llm_verified`
## Detection Method Breakdown
| Method | Count | Description |
|--------|-------|-------------|
| llm_verified | 48 | gpt-4.1 verified (party-centric + bloc-centric) |
| manual | 45 | Hand-verified mappings (includes MX/IL bloc mappings, CCD→CdL) |
| progtype_8 | 23 | Bloc-level CMP entries matched to expert-only constituents |
| name_fragment | 4 | "/" or "-" name splitting matched to expert-only parties |
| composed_comment | 2 | PartyFacts "composed of:" comments |
| progtype_5 | 1 | Average-of-members entries (Croatia ZL) |
**Total: 123 rows** mapping 116 unique expert parties to 87 union parties across 41 countries
## Bloc Coverage
Of 49 progtype=8 bloc parties in text_data, 22 (44.9%) have at least one constituent mapped. The remaining 27 have no mappable expert-only constituents because:
- Actual constituents already have their own text_data entries (e.g., KDNP, Yesh Atid, TB-LNNK)
- Actual constituents are not in expert surveys (e.g., Hadash, Ra'am, Balad, VS, DKP)
- Coalition membership is too fluid for static mapping (Panama, Colombia, Israel shifting coalitions)
- The entry is a single party coded as a bloc (e.g., German Minority, Red-Green Unity List)
## Temporary Election Coalitions (progtype=2) — REMOVED
The progtype=2 joint manifesto mappings (Italy 2001/2006/2013, France 2017, Belgium 1971) were removed in the 2026-02-08 cleanup because they mapped real individual parties as "unions" of tiny coalition partners with zero data. See "Removed Mappings" below for details.
The CCD(1767)→CdL(6241) mapping from orphan analysis remains, as CCD was a genuine long-term CdL constituent (progtype=1 in 1996, part of CdL bloc from 2001).
## Unmappable Expert-Only Parties
These parties appear in expert surveys but have no manifesto union to map to. Verified against `text_data_unfiltered.csv` (pre-temporal-filter, 1238 parties) on 2026-02-07.
**Filter retest results:** 54 of the 80 original RA task parties have manifesto data in the unfiltered text_data, but all were dropped by the temporal continuity filter (requires ≥3 years with gaps ≤6). These parties have their own CMP data but too few observations. They are correctly handled via union mapping (where applicable) or excluded (where standalone). See `scripts/party_mismatch_ra_task.csv` for full year-level detail.
| Expert PF ID | Name | Country | Manifesto years (unfiltered) | Expert years | Why unmappable |
|-------------|------|---------|------------------------------|--------------|----------------|
| 5623 | Compromís | ES | 0 (CMP codes map to different PF IDs) | 2 (2018, 2023) | No manifesto data under PF ID 5623. CMP codes 33098/33093/33914 are separate PartyFacts entries. |
| 4363 | FDG (Front de Gauche) | FR | 1 (2012) | 1 (2014) | 1 CMP year only. PCF (1251) already has its own expert data. |
| 5731 | NNP | ZA | 0 | 1 (1999) | Apartheid party that dissolved into ANC 2005. Ideologically incompatible mapping. |
| 5553 | FREPASO | AR | 1 (1995) | 4 (1995-2001) | 1 CMP year only. Independent party; Alianza (1999) was separate CMP entity. |
| 8122 | CF (Consenso Federal) | AR | 1 (2019) | 2 (2019-2020) | 1 CMP year only. One-off coalition. |
| 4182 | FPL+UCeDé | AR | 1 (2003) | 3 (1987-1991) | 1 CMP year only. Incoherent entity (UCeDé 1987 ≠ FPL coalition 2003). |
| 6160 | FR (Frente Renovador) | AR | 0 | 4 (2013-2019) | No CMP code at all. Major ideological shift 2013→2019. |
| 5879 | CD (Centro Democrático) | CO | 1 (2014) | 3 (2014-2019) | 1 CMP year only. Uribe's party, independent. |
| 4411 | PNI/PPN | CR | 0 | 1 (1974) | No CMP code. Defunct 1970s party. |
| 7412 | EK/DEK | CY | 0 | 1 (1970) | No CMP code. Defunct 1970 far-right party. |
| 3935 | United Opposition | GE | 1 (2008) | 1 (2008) | 1 CMP year only. Was anti-UNM alliance (not led by UNM). |
## Rejected Mappings
These were proposed by the RA but found incorrect during independent research:
| Individual PF ID | Name | Proposed target | Why wrong |
|-----------------|------|-----------------|-----------|
| 5623 | CC/Compromís | 81 (CCa-PNC-NC) | Name coincidence. Compromís is a Valencian left party; CCa is a Canarian right party. |
| 4363 | FDG | 1251 (PCF) | FDG was a multi-party alliance (PCF + Parti de Gauche). PCF already has its own 2014 expert data. Mapping FDG→PCF would double-count. |
| 5731 | NNP | 1219 (ANC) | The NNP (ex-apartheid National Party) dissolved into ANC in 2005 as political capitulation. NNP positions are diametrically opposed to ANC on every dimension. |
## Completeness Audit (2026-02-08)
Systematic analysis of 631 orphan expert parties (with 4+ observations) across 46 countries (excluding FPTP systems like UK, US, Canada where party unions are not a meaningful concept). Cross-referenced with MARPOR progtype data and PartyFacts metadata.
### Methodology
1. **Progtype=2 sweep**: Identified all 93 MARPOR entries with progtype=2. Grouped into 27 content-identical subgroups. Found 12 expert-only parties with progtype=2 data; 10 were mappable (mapped above), 2 had no matching text_data party in their coalition.
2. **Orphan analysis**: For each of 46 countries, identified expert-only parties not in text_data and not already mapped. Examined PartyFacts metadata (names, comments, ideology tags) and MARPOR progtype history for each.
3. **Coalition verification**: For Italian orphans specifically, checked each party's MARPOR entries for progtype=2/8 membership and ideological alignment with existing text_data coalition parties.
### Major standalone orphan parties (not coalition members)
These are significant parties with substantial expert data but no manifesto/union data. They are genuinely standalone — not missed union constituents.
| Country | Party | PF ID | Expert obs | Why standalone |
|---------|-------|-------|-----------|----------------|
| AT | BZÖ | 599 | 7 | Splinter from FPÖ; own CMP data filtered out |
| BE | PTB/PVDA | 1753 | 14 | Independent far-left; never part of any coalition |
| CZ | ANO | 2141 | 13 | Babiš party; own CMP data exists but too recent |
| CZ | Piráti | 2047 | 10 | Standalone; own CMP data exists |
| FR | REM/R | 5857 | 10 | Macron's party; too new for sufficient CMP data |
| FR | FI | 5858 | 8 | Mélenchon's party; standalone |
| IT | M5S | 2046 | 13 | Five Star Movement; progtype=1 only, standalone |
| IT | FDI | 2280 | 10 | Fratelli d'Italia; progtype=1 only, standalone |
| RO | USD | 120 | 23 | Social democratic bloc; own CMP code exists |
| SK | OĽaNO | 2130 | 13 | Populist party; standalone |
| CO | PCC | 1577 | 19 | Conservative party; own CMP code exists but filtered |
| BR | PSDB | 225 | 13 | Social democrats; standalone (not PT coalition) |
### Inactive union mappings
These union targets exist in `union_mapping.csv` but are NOT in text_data, making their constituent mappings inactive:
| Union PF ID | Name | Country | Constituents | Why inactive |
|-------------|------|---------|-------------|--------------|
| 1212 | SEL | IT | SEL(7031) | 1212 not in text_data |
| 1737 | Olive Tree | IT | PCI(34), DS(878) | 1737 not in text_data; only 2 MARPOR years |
Note: 962 (CCD+CDU→CDU-Italy) was removed from the mapping entirely because it created a chain (962 is both a constituent of UdC and a union target). See "Removed Mappings" below.
### Conclusion
The 123 mappings comprehensively cover: (1) all permanent unions with separate expert data, (2) all progtype=8 bloc parties with mappable expert-only constituents, and (3) CCD as an additional Italian coalition member identified through orphan analysis. Remaining orphan expert parties are genuinely standalone parties whose manifesto data was either filtered out by temporal continuity requirements or does not exist.
## Removed Mappings (2026-02-08)
### 11 progtype=2 joint manifesto entries removed
These entries mapped real individual parties (with substantial text and expert data) as "unions" of temporary coalition partners that had zero text data AND zero expert data. Every constituent had no information to contribute, making the mapping harmful (it reduced real parties to averages with phantom partners).
| Union PF ID | Union Name | Constituent PF ID | Constituent | Country | Why removed |
|---|---|---|---|---|---|
| 8054 | DS | 279 | M-DL | IT | M-DL has 0 text, 0 expert data; DS has 120 text, 3 expert |
| 1404 | PRC | 1635 | PdCI | IT | PdCI has 0 text, 0 expert data; PRC has 58 text, 15 expert |
| 1404 | PRC | 1711 | RnP | IT | RnP has 0 text, 0 expert data |
| 6241 | CdL | 888 | NPSI | IT | NPSI has 0 text, 0 expert data; CdL's progtype_8 mappings (AN, CeD, FI) remain |
| 6241 | CdL | 2415 | ALD | IT | ALD has 0 text, 0 expert data |
| 768 | IdV | 115 | P-UDEUR | IT | P-UDEUR has 0 text, 0 expert data; IdV has 26 text, 8 expert |
| 768 | IdV | 1369 | SVP | IT | SVP has 0 text, 0 expert data (under this PF ID) |
| 1221 | Lega | 365 | PdL | IT | PdL has 0 text, 0 expert data; Lega has 77 text, 24 expert |
| 1595 | UMP | 3229 | UDI | FR | UDI has 0 text, 0 expert data; UMP has 66 text, 18 expert |
| 49 | openVLD | 622 | CD&V | BE | CD&V is a large party (own PF ID); mapping created false dependency |
| 554 | PRL | 622 | CD&V | BE | Same issue: CD&V already has its own data pipeline |
### 2 chain mapping entries removed
These created chain dependencies (union A → union B → constituents), which the pipeline does not support:
| Union PF ID | Union Name | Constituent PF ID | Constituent | Country | Why removed |
|---|---|---|---|---|---|
| 962 | CCD+CDU | 763 | CDU (Italy) | IT | 962 is itself a constituent of UdC (201). CDU-Italy (763) has 0 data. Chain: 201→962→763. |
| 5939 | PàF | 1742 | AD | PT | 5939 is itself a constituent of CDS-PP (1308). AD (1742) has 0 data. Chain: 1308→5939→1742. |
## New Mappings Added (2026-02-08)
| Union PF ID | Union Name | Constituent PF ID | Constituent | Country | Rationale |
|---|---|---|---|---|---|
| 3979 | Salvemos a México | 1474 | PRI | MX | PRI-PVEM electoral coalition (2006-2012); PRI has extensive text + expert data |
| 3979 | Salvemos a México | 446 | PVEM | MX | PVEM is second constituent; has its own text + expert data |
| 7912 | Joint List | 421 | Hadash | IL | Arab party coalition (2015-2021); Hadash has CHES 2022 data |
| 7912 | Joint List | 1663 | Balad | IL | Balad is constituent; has CHES 2022 data |
## Excluded Alliance Labels
These party IDs appear in text_data as bloc/alliance labels but are NOT decomposed via union mapping because no constituent has data in the pipeline:
| PF ID | Name | Country | Text data | Expert data | Why excluded |
|---|---|---|---|---|---|
| 3995 | Alianza Acción Opositora | PA | 48 obs | 0 | No expert survey coverage for Panama. No constituents identifiable in pipeline. |
Note: Several other progtype=8 bloc parties in text_data also have no mapped constituents (see "Bloc Coverage" below), but they remain in the output either because (a) they have their own expert data (e.g., 2988 Georgian Dream, 3916 Alianza Grande) or (b) they function as individual parties despite bloc coding (e.g., 1527 Enhedslisten, 1439 German Minority).
## Classification Decisions
### Dual-progtype parties (both progtype=1 and progtype=8)
These 15 parties have MARPOR entries under both individual (progtype=1/3) and bloc (progtype=8) codes. Each was individually reviewed.
**Classified as individual parties (no action needed):**
| PF ID | Name | Country | Evidence |
|---|---|---|---|
| 57 | SLD | PL | Dominant Polish left party; bloc coding reflects coalition leadership, not alliance status |
| 81 | CCa | ES | Canarian regionalist party; single party with local coalition leadership |
| 1056 | SC | LV | Latvian party; dual coding reflects different election formats |
| 1150 | SDE | EE | Estonian Social Democrats; individual party |
| 1396 | Samfylkingin | IS | Icelandic Social Democratic Alliance; merged into single party |
| 1439 | MN | PL | German Minority in Poland; single ethnic party coded as bloc |
| 1527 | Enhedslisten | DK | Red-Green Alliance; functions as single party since 1989 |
| 1691 | FiDeSz-KDNP | HU | FiDeSz dominant; KDNP (1412) has separate PF ID and data |
| 2172 | ENM | GE | United National Movement; single party with bloc-era coding |
| 2228 | BYuT | UA | Tymoshenko bloc; functions as single Ukrainian party |
| 2252 | Yabloko | RU | Russian liberal party; individual entity |
**Classified as individual parties after research:**
| PF ID | Name | Country | Decision | Evidence |
|---|---|---|---|---|
| 506 | VL-TB/LNNK | LV | Individual (merger party) | National Alliance formed 2010 by merger of VL and TB/LNNK. Post-merger, functions as single party. MARPOR data 2010-2022. TB/LNNK (1704) has separate pre-merger data (1998-2014). Not mapped as union because 1704 is already a union target with its own constituents; mapping would create a chain. |
| 1586 | sp.a-SPIRIT | BE | Already handled | Already mapped as constituent of sp.a (1680) in union_mapping.csv. 0 text, 0 expert data under this PF ID. |
| 7599 | Kahol Lavan | IL | Individual party | Short-lived centrist party (2019-2020). Has own expert data (CHES 2021, V-Party 2019). Unified entity, not a multi-party alliance. |
| 2988 | Georgian Dream | GE | Individual party | Despite progtype=8 coding, functions as a single party-movement. Has own expert data (GPS 2019, V-Party 2012/2016). |
| 3916 | Alianza Grande | CO | Individual party (catch-all PF ID) | PF ID covers multiple Colombian coalitions. Has own expert data (CHES 2020). No separate constituent expert data exists. |
**Classified as alliance and mapped:**
| PF ID | Name | Country | Decision | Evidence |
|---|---|---|---|---|
| 7912 | Joint List | IL | Alliance → mapped to Hadash (421), Balad (1663) | Arab party coalition (2015-2021). GPS explicitly names 4 constituents. Hadash and Balad have separate CHES 2022 data and their own text_data. |
| 3979 | Salvemos a México | MX | Alliance → mapped to PRI (1474), PVEM (446) | PRI-PVEM electoral coalition. Both constituents have extensive text and expert data. |
## Audit Methodology
The audit script `scripts/audit_party_types.py` systematically checks every party in the output CSV:
1. **Union mapping check**: Verifies no `manifesto_pf_id` from `union_mapping.csv` appears in output (hard fail).
2. **Constituent check**: Identifies parties that are `expert_pf_id` in the mapping (expected: these are individual constituents of unions).
3. **Expert data check**: Flags parties with no expert survey data (text-only entities).
4. **Name pattern check**: Scans PartyFacts names for alliance indicators (keywords: alliance, coalition, bloc, front, union, alianza, frente; characters: +, /, &).
5. **Classification**: Each party gets one of: `individual_party`, `flagged_for_review`, `error_union_in_output`.
**To re-run after data updates:**
```bash
python3 scripts/audit_party_types.py
```
Output: `scripts/party_type_audit.csv` with columns: `party_id`, `name`, `country`, `in_union_mapping_as_union`, `in_union_mapping_as_constituent`, `has_expert_data`, `name_flags`, `classification`, `evidence`.
**Post-estimation verification** (`02_post_estimation.jl`): After extracting estimates, loads all `manifesto_pf_id` values from `union_mapping.csv` and checks none appear in the output `party_id` column. If any do, the script errors with a hard fail.
+28
View File
@@ -0,0 +1,28 @@
file_name,variable_name,label,description,type,allowed_values,range_min,range_max,missing_value_code,unit,scale_direction,constructed_from,construction_rule,notes
party_2d_election_year_panel_vN.csv.gz,party_id,PartyFacts party identifier,Identifier for the individual party,integer,,,,,identifier,,PartyFacts crosswalk,Assigned during party harmonization,
party_2d_election_year_panel_vN.csv.gz,party_name_english,English party name,Party name from the harmonized output,string,,,,,name,,Party metadata,,
party_2d_election_year_panel_vN.csv.gz,party_name_short,Short party name,Short party label from the harmonized output,string,,,,,name,,Party metadata,,May be missing
party_2d_election_year_panel_vN.csv.gz,country,Country code,ISO2 or historical country-code identifier,string,,,,,identifier,,Source metadata and PartyFacts,,
party_2d_election_year_panel_vN.csv.gz,year,Calendar year,Calendar year of the election-year estimate,integer,,1944,2025,,year,,Election and model-year metadata,,
party_2d_election_year_panel_vN.csv.gz,segment_num,Party segment number,Segment number within party after splitting at long evidence gaps,integer,,1,,,segment,,Party history segmentation,Main segment is coded 1,
party_2d_election_year_panel_vN.csv.gz,union_party_id,Union or alliance PartyFacts identifier,Identifier of parent union or alliance where applicable,integer,,,,,identifier,,Alliance mapping,,Missing if party is not represented through a union or alliance
party_2d_election_year_panel_vN.csv.gz,in_union,Union membership indicator,Indicator that the row is associated with a union or alliance,boolean,0;1,0,1,,indicator,,Alliance mapping,,
party_2d_election_year_panel_vN.csv.gz,pervote,Vote share,Vote share at the election year,numeric,,0,100,,percent,,Election metadata,,
party_2d_election_year_panel_vN.csv.gz,economic_lr,Economic left-right posterior mean,Posterior mean of economic left-right position,numeric,,0,1,,unit interval,0=left; 1=right,Posterior draws,Mean after inverse-logit transformation,
party_2d_election_year_panel_vN.csv.gz,galtan,Cultural posterior mean,Posterior mean of cultural position,numeric,,0,1,,unit interval,0=cosmopolitan/socially liberal; 1=traditionalist/nationalist,Posterior draws,Mean after inverse-logit transformation,
party_2d_election_year_panel_vN.csv.gz,economic_lr_se,Economic posterior standard error,Posterior standard deviation for economic_lr,numeric,,0,,,unit interval,,Posterior draws,Standard deviation over posterior draws,
party_2d_election_year_panel_vN.csv.gz,galtan_se,Cultural posterior standard error,Posterior standard deviation for galtan,numeric,,0,,,unit interval,,Posterior draws,Standard deviation over posterior draws,
party_2d_election_year_panel_vN.csv.gz,economic_lr_q025,Economic lower posterior interval,2.5 percent posterior quantile for economic_lr,numeric,,0,1,,unit interval,0=left; 1=right,Posterior draws,Quantile over posterior draws,
party_2d_election_year_panel_vN.csv.gz,economic_lr_q975,Economic upper posterior interval,97.5 percent posterior quantile for economic_lr,numeric,,0,1,,unit interval,0=left; 1=right,Posterior draws,Quantile over posterior draws,
party_2d_election_year_panel_vN.csv.gz,galtan_q025,Cultural lower posterior interval,2.5 percent posterior quantile for galtan,numeric,,0,1,,unit interval,0=cosmopolitan/socially liberal; 1=traditionalist/nationalist,Posterior draws,Quantile over posterior draws,
party_2d_election_year_panel_vN.csv.gz,galtan_q975,Cultural upper posterior interval,97.5 percent posterior quantile for galtan,numeric,,0,1,,unit interval,0=cosmopolitan/socially liberal; 1=traditionalist/nationalist,Posterior draws,Quantile over posterior draws,
party_2d_election_year_panel_vN.csv.gz,election_id,Election identifier,Election identifier where available,string,,,,,identifier,,Election metadata,,Missing where no election identifier is available
party_2d_election_year_panel_vN.csv.gz,election_date,Election date,Election date where available,date,,,,,date,,Election metadata,,Missing in current processed election metadata
party_2d_election_year_panel_vN.csv.gz,has_text,Text support indicator,Indicator for direct or nearby text evidence,boolean,0;1,0,1,,indicator,,Source-support construction,Nearby threshold documented in source_support_dictionary.csv,
party_2d_election_year_panel_vN.csv.gz,has_expert,Expert support indicator,Indicator for direct or nearby expert evidence,boolean,0;1,0,1,,indicator,,Source-support construction,Nearby threshold documented in source_support_dictionary.csv,
party_2d_election_year_panel_vN.csv.gz,n_text_sources,Number of text sources,Number of distinct text source families contributing direct or nearby evidence,integer,,0,,,count,,Source-support construction,,
party_2d_election_year_panel_vN.csv.gz,n_expert_sources,Number of expert sources,Number of distinct expert source families contributing direct or nearby evidence,integer,,0,,,count,,Source-support construction,,
party_2d_election_year_panel_vN.csv.gz,nearest_text_distance,Nearest text distance,Absolute distance in years to nearest text observation used to inform trajectory,numeric,,0,,,years,,Source-support construction,,Missing if no text observation exists for the party-country key
party_2d_election_year_panel_vN.csv.gz,nearest_expert_distance,Nearest expert distance,Absolute distance in years to nearest expert observation used to inform trajectory,numeric,,0,,,years,,Source-support construction,,Missing if no expert observation exists for the party-country key
party_2d_election_year_panel_vN.csv.gz,source_support_class,Source-support class,Summary class of text/expert support,string,both_direct_or_nearby; text_only_direct_or_nearby; expert_only_direct_or_nearby; temporal_propagation,,,,categorical,,Source-support construction,,
party_2d_annual_model_output_vN.csv.gz,all fields,Annual model output fields,Same model-output fields as the production annual party-position file plus no source-support augmentation,mixed,,,,,,,Posterior processing,,Secondary model output
1 file_name variable_name label description type allowed_values range_min range_max missing_value_code unit scale_direction constructed_from construction_rule notes
2 party_2d_election_year_panel_vN.csv.gz party_id PartyFacts party identifier Identifier for the individual party integer identifier PartyFacts crosswalk Assigned during party harmonization
3 party_2d_election_year_panel_vN.csv.gz party_name_english English party name Party name from the harmonized output string name Party metadata
4 party_2d_election_year_panel_vN.csv.gz party_name_short Short party name Short party label from the harmonized output string name Party metadata May be missing
5 party_2d_election_year_panel_vN.csv.gz country Country code ISO2 or historical country-code identifier string identifier Source metadata and PartyFacts
6 party_2d_election_year_panel_vN.csv.gz year Calendar year Calendar year of the election-year estimate integer 1944 2025 year Election and model-year metadata
7 party_2d_election_year_panel_vN.csv.gz segment_num Party segment number Segment number within party after splitting at long evidence gaps integer 1 segment Party history segmentation Main segment is coded 1
8 party_2d_election_year_panel_vN.csv.gz union_party_id Union or alliance PartyFacts identifier Identifier of parent union or alliance where applicable integer identifier Alliance mapping Missing if party is not represented through a union or alliance
9 party_2d_election_year_panel_vN.csv.gz in_union Union membership indicator Indicator that the row is associated with a union or alliance boolean 0;1 0 1 indicator Alliance mapping
10 party_2d_election_year_panel_vN.csv.gz pervote Vote share Vote share at the election year numeric 0 100 percent Election metadata
11 party_2d_election_year_panel_vN.csv.gz economic_lr Economic left-right posterior mean Posterior mean of economic left-right position numeric 0 1 unit interval 0=left; 1=right Posterior draws Mean after inverse-logit transformation
12 party_2d_election_year_panel_vN.csv.gz galtan Cultural posterior mean Posterior mean of cultural position numeric 0 1 unit interval 0=cosmopolitan/socially liberal; 1=traditionalist/nationalist Posterior draws Mean after inverse-logit transformation
13 party_2d_election_year_panel_vN.csv.gz economic_lr_se Economic posterior standard error Posterior standard deviation for economic_lr numeric 0 unit interval Posterior draws Standard deviation over posterior draws
14 party_2d_election_year_panel_vN.csv.gz galtan_se Cultural posterior standard error Posterior standard deviation for galtan numeric 0 unit interval Posterior draws Standard deviation over posterior draws
15 party_2d_election_year_panel_vN.csv.gz economic_lr_q025 Economic lower posterior interval 2.5 percent posterior quantile for economic_lr numeric 0 1 unit interval 0=left; 1=right Posterior draws Quantile over posterior draws
16 party_2d_election_year_panel_vN.csv.gz economic_lr_q975 Economic upper posterior interval 97.5 percent posterior quantile for economic_lr numeric 0 1 unit interval 0=left; 1=right Posterior draws Quantile over posterior draws
17 party_2d_election_year_panel_vN.csv.gz galtan_q025 Cultural lower posterior interval 2.5 percent posterior quantile for galtan numeric 0 1 unit interval 0=cosmopolitan/socially liberal; 1=traditionalist/nationalist Posterior draws Quantile over posterior draws
18 party_2d_election_year_panel_vN.csv.gz galtan_q975 Cultural upper posterior interval 97.5 percent posterior quantile for galtan numeric 0 1 unit interval 0=cosmopolitan/socially liberal; 1=traditionalist/nationalist Posterior draws Quantile over posterior draws
19 party_2d_election_year_panel_vN.csv.gz election_id Election identifier Election identifier where available string identifier Election metadata Missing where no election identifier is available
20 party_2d_election_year_panel_vN.csv.gz election_date Election date Election date where available date date Election metadata Missing in current processed election metadata
21 party_2d_election_year_panel_vN.csv.gz has_text Text support indicator Indicator for direct or nearby text evidence boolean 0;1 0 1 indicator Source-support construction Nearby threshold documented in source_support_dictionary.csv
22 party_2d_election_year_panel_vN.csv.gz has_expert Expert support indicator Indicator for direct or nearby expert evidence boolean 0;1 0 1 indicator Source-support construction Nearby threshold documented in source_support_dictionary.csv
23 party_2d_election_year_panel_vN.csv.gz n_text_sources Number of text sources Number of distinct text source families contributing direct or nearby evidence integer 0 count Source-support construction
24 party_2d_election_year_panel_vN.csv.gz n_expert_sources Number of expert sources Number of distinct expert source families contributing direct or nearby evidence integer 0 count Source-support construction
25 party_2d_election_year_panel_vN.csv.gz nearest_text_distance Nearest text distance Absolute distance in years to nearest text observation used to inform trajectory numeric 0 years Source-support construction Missing if no text observation exists for the party-country key
26 party_2d_election_year_panel_vN.csv.gz nearest_expert_distance Nearest expert distance Absolute distance in years to nearest expert observation used to inform trajectory numeric 0 years Source-support construction Missing if no expert observation exists for the party-country key
27 party_2d_election_year_panel_vN.csv.gz source_support_class Source-support class Summary class of text/expert support string both_direct_or_nearby; text_only_direct_or_nearby; expert_only_direct_or_nearby; temporal_propagation categorical Source-support construction
28 party_2d_annual_model_output_vN.csv.gz all fields Annual model output fields Same model-output fields as the production annual party-position file plus no source-support augmentation mixed Posterior processing Secondary model output
+9
View File
@@ -0,0 +1,9 @@
field,value,definition,notes
direct_support,observation in same calendar year,Observation in the same calendar year as the election-year estimate,
nearby_support,observation within threshold,Observation within the stated distance threshold of the election-year estimate,
nearby_support_threshold_years,2,Numerical threshold in years used to classify nearby support,
temporal_propagation,no nearby observation,No text or expert observation within the nearby threshold,
source_support_class,both_direct_or_nearby,Both text and expert evidence are direct or nearby,
source_support_class,text_only_direct_or_nearby,Text evidence is direct or nearby and expert evidence is not,
source_support_class,expert_only_direct_or_nearby,Expert evidence is direct or nearby and text evidence is not,
source_support_class,temporal_propagation,Neither text nor expert evidence is direct or nearby,
1 field value definition notes
2 direct_support observation in same calendar year Observation in the same calendar year as the election-year estimate
3 nearby_support observation within threshold Observation within the stated distance threshold of the election-year estimate
4 nearby_support_threshold_years 2 Numerical threshold in years used to classify nearby support
5 temporal_propagation no nearby observation No text or expert observation within the nearby threshold
6 source_support_class both_direct_or_nearby Both text and expert evidence are direct or nearby
7 source_support_class text_only_direct_or_nearby Text evidence is direct or nearby and expert evidence is not
8 source_support_class expert_only_direct_or_nearby Expert evidence is direct or nearby and text evidence is not
9 source_support_class temporal_propagation Neither text nor expert evidence is direct or nearby
+595
View File
@@ -0,0 +1,595 @@
// =============================================================================
// 2D BIPOLAR LATENT TRAIT MODEL V6 - Hierarchical L-R Weights
// =============================================================================
//
// V6 CHANGE: Hierarchical L-R weights varying by country, source, and decade
// - Replaces global simplex[2] lr_weights with additive logit-scale model
// - logit_weight[n] = global + country_offset[c] + source_offset[k] + decade_offset[d]
// - All offsets use non-centered parameterization with estimated sigma hyperparameters
// - Data-poor contexts shrink toward global mean (~55 new scalar parameters)
//
// PRESERVED FROM V5:
// - Beta-Binomial expert likelihood with K-scaling
// - V-Party cultural expansion (5 native items + v2pawelf)
// - Mean-constituent model (individual party estimates for union members)
// - Segment-based indexing (S segments, R segment-years)
// - 3-level hierarchical variance (global -> country -> family)
// - Random walk dynamics within segments
// - CDU anchor for scale identification (CDU=1375, not CDU/CSU=211)
// - Non-centered parameterization for efficiency
// - Zero-inflation model for manifesto data
// - Country-item intercepts (coding convention differences across countries)
// - Binomial-logit likelihood for text data (unchanged)
//
// =============================================================================
data {
// Segment structure
int<lower=1> S; // Number of segments
int<lower=1> P; // Number of countries
int<lower=1> R; // Total unique segment-year combinations
int<lower=1> T_year; // Total number of years
array[S] int<lower=1> len_theta_ts; // Number of years per segment
// Country membership for each segment
array[S] int<lower=1, upper=P> segment_country;
// Segment family data
int<lower=1> F; // Number of party families
array[S] int<lower=1, upper=F> segment_family; // Family index for each segment
// =========================================================================
// Text data (manifesto + PolDem)
// =========================================================================
int<lower=1> N_man; // Number of text observations
int<lower=1> K_man; // Number of unique text items
array[N_man] int<lower=1, upper=K_man> kk_man; // Text item index
array[N_man] int<lower=1, upper=S> ss_man; // Segment index (first constituent for unions)
array[N_man] int<lower=1, upper=P> pp_man; // Country index
array[N_man] int<lower=0> positive; // Positive mentions
array[N_man] int<lower=0> sample; // Total sample size
array[N_man] int<lower=1, upper=T_year> year_for_man; // Year index
// Dimension and direction for text data
array[N_man] int<lower=1, upper=2> dim_idx_man; // 1=economic, 2=galtan
array[N_man] int<lower=-1, upper=1> direction_man; // +1=right/TAN, -1=left/GAL
// V4: Constituent structure for manifesto observations
int<lower=1> N_const_man_total; // Total entries in const_rr_man
array[N_man] int<lower=1> n_const_man; // Number of constituents per obs
array[N_man] int<lower=1> const_offset_man; // Offset into const_rr_man
array[N_const_man_total] int<lower=1, upper=R> const_rr_man; // Constituent rr indices
// Country-item-year data (used by zero-inflation model)
int<lower=1> N_ciy; // Number of unique country-item-year combinations
array[N_man] int<lower=1, upper=N_ciy> ciy_idx;
// =========================================================================
// Expert dimension-specific data (V5: integer observations + scale size)
// =========================================================================
int<lower=1> N_exp_dim; // Number of dimension-specific expert observations
int<lower=1> K_exp_dim; // Number of unique expert items
array[N_exp_dim] int<lower=1, upper=K_exp_dim> kk_exp_dim;
array[N_exp_dim] int<lower=1, upper=S> ss_exp_dim;
array[N_exp_dim] int<lower=1, upper=P> pp_exp_dim;
array[N_exp_dim] int<lower=0> val_dim_int; // V5: rounded sum = round(mean * K * n_scale)
array[N_exp_dim] int<lower=1> n_total_exp_dim; // V5: K * n_scale (total trials)
array[N_exp_dim] int<lower=1> n_experts_exp_dim; // V5: K (number of experts)
// Dimension index for expert data
array[N_exp_dim] int<lower=1, upper=2> dim_idx_exp; // 1=economic, 2=galtan
// V4: Constituent structure for expert dim observations
int<lower=1> N_const_exp_dim_total;
array[N_exp_dim] int<lower=1> n_const_exp_dim;
array[N_exp_dim] int<lower=1> const_offset_exp_dim;
array[N_const_exp_dim_total] int<lower=1, upper=R> const_rr_exp_dim;
// =========================================================================
// Expert general L-R data (V5: integer observations + scale size)
// =========================================================================
int<lower=1> N_exp_lr;
int<lower=1> K_exp_lr;
array[N_exp_lr] int<lower=1, upper=K_exp_lr> kk_exp_lr;
array[N_exp_lr] int<lower=1, upper=S> ss_exp_lr;
array[N_exp_lr] int<lower=1, upper=P> pp_exp_lr;
array[N_exp_lr] int<lower=0> val_lr_int; // V5: rounded sum = round(mean * K * n_scale)
array[N_exp_lr] int<lower=1> n_total_exp_lr; // V5: K * n_scale (total trials)
array[N_exp_lr] int<lower=1> n_experts_exp_lr; // V5: K (number of experts)
// V6: Decade indexing for hierarchical L-R weights
int<lower=1> D_lr; // Number of decades
array[N_exp_lr] int<lower=1, upper=D_lr> dd_exp_lr; // Decade index per LR obs
// V4: Constituent structure for expert L-R observations
int<lower=1> N_const_exp_lr_total;
array[N_exp_lr] int<lower=1> n_const_exp_lr;
array[N_exp_lr] int<lower=1> const_offset_exp_lr;
array[N_const_exp_lr_total] int<lower=1, upper=R> const_rr_exp_lr;
// Prior information
real mn_resp_log_man;
real mn_resp_log_exp_dim;
real mn_resp_log_exp_lr;
// Identification anchor
int<lower=1, upper=S> anchor_segment; // CDU segment (1375 with unions, 211 without)
// V3 compatibility: these are still passed but not used in V4+ likelihood
// (kept so older data dicts work without modification for backwards compat)
array[N_man] int<lower=1, upper=R> rr_man; // Segment-year index (unused in V4+ likelihood)
array[N_exp_dim] int<lower=1, upper=R> rr_exp_dim;
array[N_exp_lr] int<lower=1, upper=R> rr_exp_lr;
}
transformed data {
real eps = 1e-6;
real one_minus_eps = 1 - eps;
// Count zero and non-zero samples
int N_man_zero = 0;
int N_man_nonzero = 0;
for (n in 1:N_man) {
if (sample[n] == 0) {
N_man_zero += 1;
} else {
N_man_nonzero += 1;
}
}
// Create index arrays for zero/nonzero split
array[N_man_zero > 0 ? N_man_zero : 1] int idx_zero;
array[N_man_nonzero > 0 ? N_man_nonzero : 1] int idx_nonzero;
{
int pos_zero = 1;
int pos_nonzero = 1;
for (n in 1:N_man) {
if (sample[n] == 0) {
idx_zero[pos_zero] = n;
pos_zero += 1;
} else {
idx_nonzero[pos_nonzero] = n;
pos_nonzero += 1;
}
}
}
// V4: Pre-compute constituent info for nonzero manifesto obs
array[N_man_nonzero > 0 ? N_man_nonzero : 1] int kk_man_nonzero;
array[N_man_nonzero > 0 ? N_man_nonzero : 1] int orig_idx_nonzero;
array[N_man_nonzero > 0 ? N_man_nonzero : 1] int direction_nonzero;
array[N_man_nonzero > 0 ? N_man_nonzero : 1] int pp_man_nonzero;
array[N_man_nonzero > 0 ? N_man_nonzero : 1] int dim_idx_nonzero;
array[N_man_nonzero > 0 ? N_man_nonzero : 1] int n_const_nonzero;
array[N_man_nonzero > 0 ? N_man_nonzero : 1] int const_offset_nonzero;
{
for (i in 1:N_man_nonzero) {
int n = idx_nonzero[i];
orig_idx_nonzero[i] = n;
kk_man_nonzero[i] = kk_man[n];
direction_nonzero[i] = direction_man[n];
pp_man_nonzero[i] = pp_man[n];
dim_idx_nonzero[i] = dim_idx_man[n];
n_const_nonzero[i] = n_const_man[n];
const_offset_nonzero[i] = const_offset_man[n];
}
}
// Pre-compute segment start positions
array[S + 1] int segment_start;
segment_start[1] = 1;
for (s in 1:S) {
segment_start[s + 1] = segment_start[s] + len_theta_ts[s];
}
// Extract sample and positive for nonzero observations
array[N_man_nonzero > 0 ? N_man_nonzero : 1] int sample_nonzero;
array[N_man_nonzero > 0 ? N_man_nonzero : 1] int positive_nonzero;
for (i in 1:N_man_nonzero) {
sample_nonzero[i] = sample[idx_nonzero[i]];
positive_nonzero[i] = positive[idx_nonzero[i]];
}
// Pre-compute family-to-country mapping
array[F] int family_country;
{
for (f in 1:F) {
family_country[f] = 0;
}
for (s in 1:S) {
int f = segment_family[s];
if (family_country[f] == 0) {
family_country[f] = segment_country[s];
}
}
for (f in 1:F) {
if (family_country[f] == 0) {
family_country[f] = 1;
}
}
}
}
parameters {
// =========================================================================
// Latent position parameters - 2 dimensions
// =========================================================================
matrix[2, R] theta_ncp; // Non-centered: [1]=economic_lr, [2]=galtan
matrix[2, S] theta_init_raw; // Initial traits per segment
vector<lower=0>[2] sigma_theta_init; // SD of initial theta per dimension
// =========================================================================
// Three-level hierarchical variance
// =========================================================================
vector[2] mu_sigma_global_raw; // Global mean RW variance
vector<lower=0>[2] tau_sigma_country; // Country deviation scale
matrix[2, P] sigma_country_raw; // Non-centered country deviations
vector<lower=0>[2] tau_sigma_family; // Family deviation scale
matrix[2, F] sigma_family_raw; // Non-centered family deviations
// =========================================================================
// Country-item intercepts (coding convention differences)
// =========================================================================
matrix[P, K_man] country_item_raw;
real<lower=0> sigma_country_item;
// =========================================================================
// Zero-sample parameters
// =========================================================================
real alpha_zs;
vector[T_year] year_effect_raw;
real<lower=0> sigma_year_effect;
vector[S] segment_zs_raw;
real<lower=0> sigma_segment_zs;
vector[N_ciy] ciy_zs_raw;
real<lower=0> sigma_ciy_zs;
// =========================================================================
// Item parameters
// =========================================================================
// Text data: intercept + single positive loading (direction handled in data)
vector[K_man] gamma_man_intercept_raw;
vector<lower=0>[K_man] gamma_man_loading; // Positive loading
// Expert dimension-specific: intercept + slope (direct mapping to dimension)
vector[K_exp_dim] gamma_exp_intercept_raw;
vector<lower=0>[K_exp_dim] gamma_exp_slope;
// Expert general L-R: intercept + slope
vector[K_exp_lr] gamma_lr_intercept_raw;
vector<lower=0>[K_exp_lr] gamma_lr_slope;
// V6: Hierarchical L-R weights (replace simplex[2] lr_weights)
real lr_weight_global; // Global logit-scale weight
vector[P] lr_country_offset_raw; // Country offsets (non-centered)
vector[K_exp_lr] lr_source_offset_raw; // Source offsets (non-centered)
vector[D_lr] lr_decade_offset_raw; // Decade offsets (non-centered)
real<lower=0> sigma_lr_country; // SD of country offsets
real<lower=0> sigma_lr_source; // SD of source offsets
real<lower=0> sigma_lr_decade; // SD of decade offsets
// Precision parameters
real<lower=0> phi_exp_dim; // Precision for dimension-specific (now: only measurement noise)
real<lower=0> phi_exp_lr; // Precision for general L-R (now: only measurement noise)
// Scale parameters
real<lower=0> sigma_intercept_man;
real<lower=0> sigma_loading_man;
real<lower=0> sigma_intercept_exp;
real<lower=0> sigma_slope_exp;
real<lower=0> sigma_intercept_lr;
real<lower=0> sigma_slope_lr;
real mu_lambda_man;
real mu_lambda_exp_dim;
real mu_lambda_exp_lr;
}
transformed parameters {
matrix[2, R] theta; // [1]=economic_lr, [2]=galtan
matrix[2, S] theta_init;
// Three-level hierarchical variance (2D)
matrix[2, P] sigma_theta_country;
for (d in 1:2) {
for (p in 1:P) {
sigma_theta_country[d, p] = log1p_exp(
mu_sigma_global_raw[d] + tau_sigma_country[d] * sigma_country_raw[d, p]
);
}
}
matrix[2, F] sigma_theta_family;
for (d in 1:2) {
for (f in 1:F) {
int c = family_country[f];
sigma_theta_family[d, f] = log1p_exp(
log(sigma_theta_country[d, c]) + tau_sigma_family[d] * sigma_family_raw[d, f]
);
}
}
// Non-centered parameterization for theta_init
for (dim in 1:2) {
theta_init[dim, :] = sigma_theta_init[dim] * theta_init_raw[dim, :];
}
// Country-item intercepts
matrix[P, K_man] country_item_intercept = sigma_country_item * country_item_raw;
// Zero-sample components
vector[T_year] year_effect = sigma_year_effect * year_effect_raw;
vector[S] segment_zs = sigma_segment_zs * segment_zs_raw;
vector[N_ciy] ciy_zs = sigma_ciy_zs * ciy_zs_raw;
// Zero-sample probability
vector[N_man] zero_sample_logit = alpha_zs +
year_effect[year_for_man] +
segment_zs[ss_man] +
ciy_zs[ciy_idx];
vector[N_man] zero_sample_prob = inv_logit(zero_sample_logit);
zero_sample_prob = fmax(fmin(zero_sample_prob, one_minus_eps), eps);
// Construct theta using family-specific random walk variance (2D)
for (dim in 1:2) {
for (s in 1:S) {
int start = segment_start[s];
int Ts = len_theta_ts[s];
int fam = segment_family[s];
real sigma_s = sigma_theta_family[dim, fam];
theta[dim, start] = theta_init[dim, s] + sigma_s * theta_ncp[dim, start];
if (Ts > 1) {
theta[dim, start + 1 : start + Ts - 1] = theta[dim, start] +
cumulative_sum(sigma_s * theta_ncp[dim, start + 1 : start + Ts - 1]);
}
}
}
// Item parameters
vector[K_man] gamma_man_intercept = mu_lambda_man + sigma_intercept_man * gamma_man_intercept_raw;
vector[K_exp_dim] gamma_exp_intercept = mu_lambda_exp_dim + sigma_intercept_exp * gamma_exp_intercept_raw;
vector[K_exp_lr] gamma_lr_intercept = mu_lambda_exp_lr + sigma_intercept_lr * gamma_lr_intercept_raw;
}
model {
// =========================================================================
// PRIORS
// =========================================================================
// Three-level hierarchical variance priors (2D)
mu_sigma_global_raw ~ normal(-0.8, 0.5);
tau_sigma_country ~ normal(0, 0.3);
to_vector(sigma_country_raw) ~ std_normal();
tau_sigma_family ~ normal(0, 0.2);
to_vector(sigma_family_raw) ~ std_normal();
// Other variance priors
sigma_theta_init ~ normal(0, 0.5);
to_vector(theta_init_raw) ~ std_normal();
to_vector(theta_ncp) ~ std_normal();
// Country-item intercept priors
to_vector(country_item_raw) ~ std_normal();
sigma_country_item ~ normal(0, 0.3);
// Zero-sample priors
alpha_zs ~ normal(-1, 1);
year_effect_raw ~ std_normal();
sigma_year_effect ~ normal(0, 0.5);
segment_zs_raw ~ std_normal();
sigma_segment_zs ~ normal(0, 0.3);
ciy_zs_raw ~ std_normal();
sigma_ciy_zs ~ normal(0, 0.3);
// =========================================================================
// Item parameter priors
// =========================================================================
// Text data item priors
mu_lambda_man ~ normal(mn_resp_log_man, 0.5);
sigma_intercept_man ~ normal(0, 1);
sigma_loading_man ~ normal(0, 0.5);
gamma_man_intercept_raw ~ std_normal();
gamma_man_loading ~ normal(1.0, sigma_loading_man);
// Expert dimension item priors
mu_lambda_exp_dim ~ normal(mn_resp_log_exp_dim, 0.5);
sigma_intercept_exp ~ normal(0, 1);
sigma_slope_exp ~ normal(0, 0.5);
gamma_exp_intercept_raw ~ std_normal();
gamma_exp_slope ~ normal(1.0, sigma_slope_exp);
// Expert L-R item priors
mu_lambda_exp_lr ~ normal(mn_resp_log_exp_lr, 0.5);
sigma_intercept_lr ~ normal(0, 1);
sigma_slope_lr ~ normal(0, 0.5);
gamma_lr_intercept_raw ~ std_normal();
gamma_lr_slope ~ normal(1.0, sigma_slope_lr);
// V6: Hierarchical L-R weight priors
lr_weight_global ~ normal(0, 1);
lr_country_offset_raw ~ std_normal();
lr_source_offset_raw ~ std_normal();
lr_decade_offset_raw ~ std_normal();
sigma_lr_country ~ normal(0, 0.5);
sigma_lr_source ~ normal(0, 0.5);
sigma_lr_decade ~ normal(0, 0.5);
// Expert data precision priors
phi_exp_dim ~ gamma(50, 0.5);
phi_exp_lr ~ gamma(10, 0.5);
// =========================================================================
// CDU ANCHOR CONSTRAINT
// With unions: anchors CDU (1375) at moderate center-right position
// Without unions: anchors CDU/CSU (211) as before
// =========================================================================
target += normal_lpdf(theta_init[1, anchor_segment] | 0.2, 0.2); // economic_lr
target += normal_lpdf(theta_init[2, anchor_segment] | 0.2, 0.2); // galtan
// =========================================================================
// LIKELIHOOD 1: Text data (binomial with zero-inflation)
// V4: Mean-constituent averaging for union observations
// =========================================================================
// Zero-sample observations
if (N_man_zero > 0) {
target += sum(log(zero_sample_prob[idx_zero]));
}
// Non-zero observations with constituent averaging
if (N_man_nonzero > 0) {
vector[N_man_nonzero] lin_man;
for (i in 1:N_man_nonzero) {
int nc = n_const_nonzero[i];
int off = const_offset_nonzero[i];
int dim = dim_idx_nonzero[i];
real avg_pos;
if (nc == 1) {
// Fast path: single party (>95% of observations)
avg_pos = theta[dim, const_rr_man[off]];
} else {
// Union: average over constituent thetas
avg_pos = 0;
for (c in 0:(nc-1)) {
avg_pos += theta[dim, const_rr_man[off + c]];
}
avg_pos /= nc;
}
lin_man[i] = gamma_man_intercept[kk_man_nonzero[i]] +
direction_nonzero[i] * gamma_man_loading[kk_man_nonzero[i]] * avg_pos +
country_item_intercept[pp_man_nonzero[i], kk_man_nonzero[i]];
}
for (i in 1:N_man_nonzero) {
target += log1m(zero_sample_prob[orig_idx_nonzero[i]]) +
binomial_logit_lpmf(positive_nonzero[i] | sample_nonzero[i], lin_man[i]);
}
}
// =========================================================================
// LIKELIHOOD 2: Expert dimension-specific data (V5: beta-binomial likelihood)
// V4: Constituent averaging for union-level expert obs
// =========================================================================
{
vector[N_exp_dim] pos;
for (n in 1:N_exp_dim) {
int nc = n_const_exp_dim[n];
int off = const_offset_exp_dim[n];
int dim = dim_idx_exp[n];
if (nc == 1) {
pos[n] = theta[dim, const_rr_exp_dim[off]];
} else {
pos[n] = 0;
for (c in 0:(nc-1)) {
pos[n] += theta[dim, const_rr_exp_dim[off + c]];
}
pos[n] /= nc;
}
}
vector[N_exp_dim] lin_exp_dim;
for (n in 1:N_exp_dim) {
lin_exp_dim[n] = gamma_exp_intercept[kk_exp_dim[n]] +
gamma_exp_slope[kk_exp_dim[n]] * pos[n];
}
vector[N_exp_dim] mu_exp_dim = inv_logit(lin_exp_dim);
mu_exp_dim = fmax(fmin(mu_exp_dim, one_minus_eps), eps);
// K-scaling: phi * K corrects for independent expert perceptions
vector[N_exp_dim] alpha_exp_dim = phi_exp_dim * to_vector(n_experts_exp_dim) .* mu_exp_dim;
vector[N_exp_dim] beta_exp_dim = phi_exp_dim * to_vector(n_experts_exp_dim) .* (1 - mu_exp_dim);
val_dim_int ~ beta_binomial(n_total_exp_dim, alpha_exp_dim, beta_exp_dim);
}
// =========================================================================
// LIKELIHOOD 3: Expert general L-R data (V6: hierarchical per-obs weights)
// V4: Constituent averaging + weighted combination of both dimensions
// V6: Per-observation weights via country + source + decade offsets
// =========================================================================
{
// V6: Compute per-observation economic weight on logit scale
vector[N_exp_lr] logit_w;
for (n in 1:N_exp_lr) {
logit_w[n] = lr_weight_global
+ sigma_lr_country * lr_country_offset_raw[pp_exp_lr[n]]
+ sigma_lr_source * lr_source_offset_raw[kk_exp_lr[n]]
+ sigma_lr_decade * lr_decade_offset_raw[dd_exp_lr[n]];
}
vector[N_exp_lr] w_econ = inv_logit(logit_w);
vector[N_exp_lr] combined_pos;
for (n in 1:N_exp_lr) {
int nc = n_const_exp_lr[n];
int off = const_offset_exp_lr[n];
if (nc == 1) {
int r = const_rr_exp_lr[off];
combined_pos[n] = w_econ[n] * theta[1, r] + (1 - w_econ[n]) * theta[2, r];
} else {
// Average the combined position across constituents
combined_pos[n] = 0;
for (c in 0:(nc-1)) {
int r = const_rr_exp_lr[off + c];
combined_pos[n] += w_econ[n] * theta[1, r] + (1 - w_econ[n]) * theta[2, r];
}
combined_pos[n] /= nc;
}
}
vector[N_exp_lr] lin_exp_lr;
for (n in 1:N_exp_lr) {
lin_exp_lr[n] = gamma_lr_intercept[kk_exp_lr[n]] +
gamma_lr_slope[kk_exp_lr[n]] * combined_pos[n];
}
vector[N_exp_lr] mu_exp_lr = inv_logit(lin_exp_lr);
mu_exp_lr = fmax(fmin(mu_exp_lr, one_minus_eps), eps);
// K-scaling: phi * K corrects for independent expert perceptions
vector[N_exp_lr] alpha_exp_lr = phi_exp_lr * to_vector(n_experts_exp_lr) .* mu_exp_lr;
vector[N_exp_lr] beta_exp_lr = phi_exp_lr * to_vector(n_experts_exp_lr) .* (1 - mu_exp_lr);
val_lr_int ~ beta_binomial(n_total_exp_lr, alpha_exp_lr, beta_exp_lr);
}
}
generated quantities {
// =========================================================================
// Direct outputs (no derivation needed)
// =========================================================================
// Two bipolar scales on [0,1] via inv_logit
vector[R] economic_lr = inv_logit(to_vector(theta[1, :])); // 0=left, 1=right
vector[R] galtan = inv_logit(to_vector(theta[2, :])); // 0=GAL, 1=TAN
// General left-right combining both dimensions (using global weight only)
// Per-R country/source/decade info not available in GQ, so use global mean
real lr_w_econ_global = inv_logit(lr_weight_global);
vector[R] general_lr = inv_logit(
lr_w_econ_global * to_vector(theta[1, :]) + (1 - lr_w_econ_global) * to_vector(theta[2, :])
);
// V6: Expose hierarchical L-R weight diagnostics
real lr_weight_econ_global = lr_w_econ_global;
real lr_sigma_country = sigma_lr_country;
real lr_sigma_source = sigma_lr_source;
real lr_sigma_decade = sigma_lr_decade;
// Expose hierarchical variance diagnostics (2D)
vector[2] mean_sigma_global;
vector[2] mean_sigma_country;
vector[2] mean_sigma_family;
for (d in 1:2) {
mean_sigma_global[d] = log1p_exp(mu_sigma_global_raw[d]);
mean_sigma_country[d] = mean(sigma_theta_country[d, :]);
mean_sigma_family[d] = mean(sigma_theta_family[d, :]);
}
}
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
MODE="${1:-reuse}"
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
cd "$repo_root"
case "$MODE" in
full|reuse|dry-run) ;;
*)
echo "Usage: $0 [full|reuse|dry-run]" >&2
exit 1
;;
esac
export PARTY2D_RAW_DATA_DIR="${PARTY2D_RAW_DATA_DIR:-$repo_root/_local/raw}"
export TMPDIR="${TMPDIR:-$repo_root/_local/tmp}"
mkdir -p "$TMPDIR"
if [ "$MODE" = "dry-run" ]; then
echo "Checking commands..."
command -v bash >/dev/null
command -v Rscript >/dev/null
command -v julia >/dev/null
echo "Checking key files..."
test -f Project.toml
test -f Manifest.toml
test -f models/stan_model_2dim_v6.stan
test -f data/text_data.csv
test -f data/expert.csv
test -f data/lr_data.csv
test -f data/model_data.csv
echo "Checking shell syntax..."
bash -n scripts/01_prepare_data.sh
bash -n scripts/02_fit_model.sh
bash -n scripts/03_extract_estimates.sh
bash -n scripts/04_enrich_estimates.sh
bash -n scripts/05_validate_estimates.sh
echo "Checking R syntax..."
Rscript -e 'files <- list.files("src/r", pattern="\\.R$", full.names=TRUE); invisible(lapply(files, parse))'
echo "Checking Julia project can instantiate without running model code..."
julia --project=. -e 'import Pkg; Pkg.instantiate(); println("Julia project OK")'
echo "Dry run passed. No model fitting was run."
exit 0
fi
bash scripts/01_prepare_data.sh
if [ "$MODE" = "full" ]; then
bash scripts/02_fit_model.sh
else
echo "reuse: skipping Stan model fitting; using latest existing model output"
fi
bash scripts/03_extract_estimates.sh
bash scripts/04_enrich_estimates.sh
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
cd "$repo_root"
Rscript -e 'setwd("data"); source("../src/r/00_data-management.R")'
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
cd "$repo_root"
julia --project=. src/julia/01_run_model.jl
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
cd "$repo_root"
julia --project=. src/julia/02_post_estimation.jl
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
cd "$repo_root"
julia --project=. src/julia/02_enrich_output.jl
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
cd "$repo_root"
julia --project=. src/julia/validate_convergent.jl
julia --project=. src/julia/validate_uncertainty.jl
julia --project=. src/julia/validate_construct.jl
+340
View File
@@ -0,0 +1,340 @@
#!/usr/bin/env julia
#############################################################################
## run_model.jl
## Main runner for latent trait model
## Executes the complete pipeline: data loading → preparation → model fitting
##
## Supports two model versions:
## - "2dim": 2D bipolar model (V1) - estimates economic_lr and galtan directly
## - "4dim": 4D unipolar model (V10) - estimates 4 traits, derives 2 scales
##
## Default is 2D model (better identification, faster convergence)
#############################################################################
using Dates
#############################################################################
## EXECUTION CONFIGURATION (Change these values as needed)
#############################################################################
const MODEL_VERSION = "2dim" # "2dim" (recommended) or "4dim"
const STAN_MODEL_FILE = MODEL_VERSION == "2dim" ? "models/stan_model_2dim_v6.stan" : "models/stan_model_4dim_v10.stan"
const NUM_CHAINS = 4 # Number of MCMC chains (run in parallel)
const NUM_WARMUP = 1000 # Number of warmup iterations
const NUM_SAMPLES = 2000 # Number of sampling iterations
const ADAPT_DELTA = 0.95 # Target acceptance probability
const MAX_DEPTH = 15 # Maximum tree depth
const START_YEAR = 1944 # First year to include (avoid sparse early data)
println("=" ^ 70)
println("Latent Trait Model - Estimation Pipeline")
println("=" ^ 70)
println("Started at: ", Dates.now())
println("Configuration:")
println(" Model version: $(MODEL_VERSION)")
println(" Stan model: $(STAN_MODEL_FILE)")
println(" Chains: $(NUM_CHAINS)")
println(" Warmup iterations: $(NUM_WARMUP)")
println(" Sampling iterations: $(NUM_SAMPLES)")
println(" Total iterations per chain: $(NUM_WARMUP + NUM_SAMPLES)")
println(" Adapt delta: $(ADAPT_DELTA)")
println(" Max depth: $(MAX_DEPTH)")
println(" Start year: $(START_YEAR)")
if MODEL_VERSION == "2dim"
println("\n 2D MODEL: Estimates economic_lr and galtan directly")
println(" (Half the parameters, better convergence)")
else
println("\n 4D MODEL: Estimates 4 traits, derives 2 scales")
println(" (Known identification issues - see VERSION_HISTORY.md)")
end
println("=" ^ 70)
# Include pipeline modules
include("pipeline/00_validation.jl") # Validation checks
include("pipeline/02_data_loading.jl")
include("pipeline/03_data_preparation.jl")
include("pipeline/04_model_execution.jl")
include("pipeline/05_results_processing.jl")
# Load robust save module
include("pipeline/06_save_model.jl")
import .RobustSave: robust_save_model
function run_model(;
num_chains=NUM_CHAINS,
num_warmup=NUM_WARMUP,
num_samples=NUM_SAMPLES,
adapt_delta=ADAPT_DELTA,
max_depth=MAX_DEPTH,
model_file=STAN_MODEL_FILE,
start_year=START_YEAR,
data_dir="data"
)
"""Run the complete latent trait model pipeline (2D or 4D based on MODEL_VERSION)"""
try
# Step 1: Load and preprocess data
println("\n" * "="^50)
println("STEP 1: DATA LOADING")
println("="^50)
manifesto, expert_dim, expert_lr, year0, union_to_constituents, constituent_to_union = load_and_preprocess_4dim_data(start_year; data_dir=data_dir)
# Step 2: Prepare Stan data structure
println("\n" * "="^50)
println("STEP 2: DATA PREPARATION")
println("="^50)
# Prepare indices and mappings (V4: union-aware)
data_prep = prepare_4dim_stan_data(manifesto, expert_dim, expert_lr, year0;
union_to_constituents=union_to_constituents,
constituent_to_union=constituent_to_union)
# Finalize Stan data dictionary (V4: includes constituent arrays)
final_data = finalize_4dim_stan_data(
data_prep.manifesto, data_prep.expert_dim, data_prep.expert_lr,
data_prep.segment_year, data_prep.segment_info,
data_prep.all_parties, data_prep.all_groups,
data_prep.group_to_index, year0, data_prep.S, data_prep.J, data_prep.P, data_prep.R,
data_prep.N_ciy, data_prep.len_theta_ts, data_prep.segment_country_idx,
data_prep.F, data_prep.segment_family_idx, data_prep.anchor_segment_idx;
N_const_man_total=data_prep.N_const_man_total,
n_const_man=data_prep.n_const_man,
const_offset_man=data_prep.const_offset_man,
const_rr_man=data_prep.const_rr_man,
N_const_exp_dim_total=data_prep.N_const_exp_dim_total,
n_const_exp_dim=data_prep.n_const_exp_dim,
const_offset_exp_dim=data_prep.const_offset_exp_dim,
const_rr_exp_dim=data_prep.const_rr_exp_dim,
N_const_exp_lr_total=data_prep.N_const_exp_lr_total,
n_const_exp_lr=data_prep.n_const_exp_lr,
const_offset_exp_lr=data_prep.const_offset_exp_lr,
const_rr_exp_lr=data_prep.const_rr_exp_lr
)
dat_4dim = final_data.dat_4dim
# Step 3: Validate data BEFORE running Stan
println("\n" * "="^50)
println("STEP 3: DATA VALIDATION")
println("="^50)
if !validate_stan_data(dat_4dim; verbose=true)
error("Data validation failed - see errors above")
end
estimate_memory_requirements(dat_4dim; verbose=true)
# Step 4: Create initialization function
println("\n" * "="^50)
println("STEP 4: MODEL INITIALIZATION")
println("="^50)
# Determine model version for initialization
model_init_version = MODEL_VERSION == "2dim" ? "v1_2dim" : "v10"
# Use S (segments) for initialization, not J (parties)
init_fn = create_init_function(dat_4dim, data_prep.S, data_prep.P,
data_prep.R, final_data.T_year, data_prep.N_ciy;
model_version=model_init_version)
# Validate initialization values
println("\nValidating initialization for chain 1...")
test_init = init_fn()
if !validate_init_values(test_init; verbose=true)
error("Initialization validation failed - see errors above")
end
# Step 5: Run Stan model
println("\n" * "="^50)
println("STEP 5: MODEL EXECUTION")
println("="^50)
# Create temp folder for output
temp_folder = mktempdir()
println("Temporary folder for Stan output: $temp_folder")
# Run Stan model
stanmodel = run_4dim_stan_model(
dat_4dim, init_fn, temp_folder;
num_chains=num_chains,
num_warmup=num_warmup,
num_samples=num_samples,
adapt_delta=adapt_delta,
max_depth=max_depth,
model_file=model_file
)
# Step 6: Results Processing & Diagnostics
println("\n" * "="^50)
println("STEP 6: RESULTS PROCESSING & DIAGNOSTICS")
println("="^50)
results = extract_model_results_4dim(stanmodel)
diagnostics = compute_model_diagnostics(stanmodel)
println("\nCONVERGENCE DIAGNOSTICS:")
println(" Max R-hat: $(round(diagnostics.max_rhat, digits=4))")
println(" Mean R-hat: $(round(diagnostics.mean_rhat, digits=4))")
println(" High R-hat count: $(diagnostics.high_rhat_count)")
println(" Min ESS: $(round(diagnostics.min_ess, digits=0))")
println(" Mean ESS: $(round(diagnostics.mean_ess, digits=0))")
println(" Convergence Status: $(diagnostics.convergence_status)")
# Step 7: Save results
println("\n" * "="^50)
println("STEP 7: SAVING RESULTS")
println("="^50)
println(" Using save-local-then-move strategy...")
# Prepare data for saving (SINGLE copy of stanmodel, not multiple!)
model_data_to_save = Dict{String, Any}(
# StanModel object (contains all MCMC samples)
"stanmodel_object" => stanmodel,
# Processed diagnostics
"diagnostics_summary" => diagnostics.diagnostics_summary,
"data_dict" => dat_4dim,
# Original data for reference
"manifesto" => final_data.manifesto,
"expert_dim" => final_data.expert_dim,
"expert_lr" => final_data.expert_lr,
"segment_year" => final_data.segment_year, # V10: segment-year mapping
"segment_info" => final_data.segment_info, # V10: segment metadata (party_id, segment_num, year range)
# Metadata with convergence info
"model_info" => Dict(
"timestamp" => Dates.format(Dates.now(), "yyyy-mm-dd_HH-MM-SS"),
"max_rhat" => diagnostics.max_rhat,
"mean_rhat" => diagnostics.mean_rhat,
"min_ess" => diagnostics.min_ess,
"mean_ess" => diagnostics.mean_ess,
"convergence_status" => diagnostics.convergence_status,
"model_file" => model_file,
"model_version" => MODEL_VERSION,
"num_chains" => num_chains,
"num_warmup" => num_warmup,
"num_samples" => num_samples,
"adapt_delta" => adapt_delta,
"max_depth" => max_depth,
"year0" => year0,
"dimensions" => MODEL_VERSION == "2dim" ?
["economic_lr", "galtan"] :
["pro_market", "pro_welfare", "cosmopolitan", "traditional"]
)
)
# Save using CSV-first robust system. Chains have already been secured
# by model execution; robust_save_model adds metadata/data and verifies.
save_dir = data_dir != "data" ? joinpath(data_dir, "model_run") : "outputs/model_outputs"
output_file = robust_save_model(
stanmodel,
model_data_to_save,
save_dir;
compress=true, # Ignored by CSV-first save implementation
keep_local_backups=2 # Ignored; chains are already saved before this step
)
# Robust save module already verified everything!
println("\n" * "="^70)
println("MODEL EXECUTION COMPLETED SUCCESSFULLY!")
println("="^70)
println(" Max R-hat: $(round(diagnostics.max_rhat, digits=4))")
println(" Mean R-hat: $(round(diagnostics.mean_rhat, digits=4))")
println(" Convergence: $(diagnostics.convergence_status)")
println(" Output file: $output_file")
# Print summary statistics
println("\nModel Summary ($(MODEL_VERSION == "2dim" ? "2D Direct Bipolar" : "4D Unipolar")):")
println(" Segments: $(data_prep.S)")
println(" Parties with valid segments: $(data_prep.J)")
println(" Countries: $(data_prep.P)")
println(" Segment-year combinations: $(data_prep.R)")
println(" Years: $(final_data.T_year)")
println(" Manifesto observations: $(dat_4dim["N_man"])")
println(" Expert dimension-specific observations: $(dat_4dim["N_exp_dim"])")
println(" Expert general L-R observations: $(dat_4dim["N_exp_lr"])")
if MODEL_VERSION == "2dim"
println(" Dimensions estimated: 2 (economic_lr, galtan)")
println(" Theta parameters: $(2 * data_prep.R) (2 × R)")
else
println(" Dimensions estimated: 4 (pro_market, pro_welfare, cosmopolitan, traditional)")
println(" Theta parameters: $(4 * data_prep.R) (4 × R)")
end
println("=" ^ 70)
# Cleanup temp folder after successful save
println("\nCLEANING UP TEMPORARY FILES...")
try
if isdir(temp_folder)
rm(temp_folder, recursive=true, force=true)
println(" Removed temporary folder: $temp_folder")
end
catch cleanup_error
println(" Warning: Could not remove temp folder: $cleanup_error")
println(" (This won't affect your saved results)")
end
return true
catch e
println("\nERROR in model pipeline: $e")
println("Stack trace:")
showerror(stdout, e, catch_backtrace())
rethrow(e)
end
end
function main(args=ARGS)
# Parse --data-dir argument
data_dir = "data"
for (i, arg) in enumerate(args)
if arg == "--data-dir" && i < length(args)
data_dir = args[i + 1]
elseif startswith(arg, "--data-dir=")
data_dir = split(arg, "=", limit=2)[2]
end
end
if data_dir != "."
println("Using data directory: $data_dir")
end
println("Executing $(MODEL_VERSION) latent trait model pipeline...")
# Check that required data files exist (in data_dir)
required_data = [joinpath(data_dir, f) for f in ["text_data.csv", "expert.csv", "lr_data.csv"]]
required_files = vcat(required_data, [STAN_MODEL_FILE])
missing_files = []
for file in required_files
if !isfile(file)
push!(missing_files, file)
end
end
if !isempty(missing_files)
println("ERROR: Missing required files:")
for file in missing_files
println(" - $file")
end
if any(f -> endswith(f, "text_data.csv") || endswith(f, "expert.csv") || endswith(f, "lr_data.csv"), missing_files)
println("\nTo generate data files, run:")
println(" bash scripts/01_prepare_data.sh")
end
error("Cannot proceed without required files")
end
# Run the complete pipeline
results = run_model(data_dir=data_dir)
println("\n$(MODEL_VERSION) latent trait model pipeline completed successfully!")
println("Check outputs/model_outputs/latest/ for chain CSVs and metadata.")
end
# Main execution
if abspath(PROGRAM_FILE) == @__FILE__
main()
end
+211
View File
@@ -0,0 +1,211 @@
#!/usr/bin/env julia
#=
02_enrich_output.jl - Enrich party positions CSV with metadata
Fast post-processing script that adds party names, union membership status,
and election results to the model output CSV. Operates purely on CSV files
(no chain loading). Takes seconds, not minutes.
Usage:
julia 02_enrich_output.jl # enriches latest party_positions_*.csv
julia 02_enrich_output.jl somefile.csv # enriches a specific file
Adds columns:
party_name_english - Full English party name (from data/party_names.csv)
party_name_short - Standard abbreviation
in_union - 1 if party's union had a joint manifesto that year, 0 otherwise
pervote - Vote share (%) at election years; missing for non-election years
=#
using CSV
using DataFrames
using Dates
function find_latest_output()
outdir = "outputs/estimations/latest"
files = filter(f -> startswith(f, "party_positions_") && endswith(f, ".csv") &&
!contains(f, "metadata") && !contains(f, "tables"),
readdir(outdir))
isempty(files) && error("No party_positions_*.csv found in $outdir/")
sort!(files, rev=true)
return joinpath(outdir, files[1])
end
function enrich(input_file::String)
println("="^60)
println("ENRICH OUTPUT")
println("="^60)
println("Input: $input_file")
output = CSV.read(input_file, DataFrame)
println(" Rows: $(nrow(output)), Columns: $(ncol(output))")
# --- Party names ---
party_names_file = joinpath("data", "party_names.csv")
if isfile(party_names_file)
names_df = CSV.read(party_names_file, DataFrame)
name_lookup = Dict{Int, Tuple{String, String}}()
for row in eachrow(names_df)
short = ismissing(row.party_name_short) ? "" : string(row.party_name_short)
name_lookup[row.partyfacts_id] = (string(row.party_name_english), short)
end
output.party_name_english = [
haskey(name_lookup, pid) ? name_lookup[pid][1] : ""
for pid in output.party_id
]
output.party_name_short = [
haskey(name_lookup, pid) ? name_lookup[pid][2] : ""
for pid in output.party_id
]
n_named = count(x -> x != "", output.party_name_english)
println(" Party names: $n_named / $(nrow(output)) rows matched")
else
output.party_name_english = fill("", nrow(output))
output.party_name_short = fill("", nrow(output))
println(" WARNING: data/party_names.csv not found")
end
# --- Union mapping (for in_union + pervote fallback) ---
union_mapping_file = joinpath("data", "union_mapping.csv")
constituent_to_union = Dict{Int, Int}()
if isfile(union_mapping_file)
union_df = CSV.read(union_mapping_file, DataFrame)
for row in eachrow(union_df)
constituent_to_union[row.expert_pf_id] = row.manifesto_pf_id
end
end
# --- in_union dummy (year-varying) ---
text_data_file = "data/text_data.csv"
output.in_union = zeros(Int, nrow(output))
if isfile(text_data_file) && !isempty(constituent_to_union)
text_df = CSV.read(text_data_file, DataFrame)
# Build set of (party_pf_id, year) pairs for manifesto data
manifesto_text = filter(r -> r.project == "Manifesto Project", text_df)
manifesto_party_years = Set{Tuple{Int, Int}}()
for row in eachrow(manifesto_text)
push!(manifesto_party_years, (row.party, row.year))
end
# Process each (party, segment) group
gdf = groupby(output, [:party_id, :segment_num])
for subdf in gdf
pid = subdf.party_id[1]
!haskey(constituent_to_union, pid) && continue
union_id = constituent_to_union[pid]
# Get row indices in the full output for this group
idxs = parentindices(subdf)[1]
# Determine in_union at election years
election_year_vals = Dict{Int, Int}()
for (j, row) in enumerate(eachrow(subdf))
if (union_id, row.year) in manifesto_party_years
election_year_vals[row.year] = 1
elseif (pid, row.year) in manifesto_party_years
election_year_vals[row.year] = 0
end
end
# Forward-fill within segment
sorted_pairs = sort(collect(zip(subdf.year, idxs)))
last_val = 0
for (yr, idx) in sorted_pairs
if haskey(election_year_vals, yr)
last_val = election_year_vals[yr]
end
output.in_union[idx] = last_val
end
end
n_in_union = count(x -> x == 1, output.in_union)
println(" in_union: $n_in_union rows flagged as union members")
else
println(" WARNING: Could not compute in_union (missing files)")
end
# --- Election results (pervote) ---
election_file = joinpath("data", "election_data.csv")
output.pervote = Vector{Union{Float64, Missing}}(missing, nrow(output))
if isfile(election_file)
election_df = CSV.read(election_file, DataFrame)
# Build lookup: (party_id, year) -> pervote
election_lookup = Dict{Tuple{Int, Int}, Float64}()
for row in eachrow(election_df)
election_lookup[(row.party, row.year)] = row.pervote
end
n_filled = 0
for i in 1:nrow(output)
pid = output.party_id[i]
yr = output.year[i]
# Direct match (standalone party)
if haskey(election_lookup, (pid, yr))
output.pervote[i] = election_lookup[(pid, yr)]
n_filled += 1
elseif hasproperty(output, :union_party_id) && !ismissing(output.union_party_id[i])
# Union constituent: look up by union PF ID
uid = output.union_party_id[i]
if haskey(election_lookup, (uid, yr))
output.pervote[i] = election_lookup[(uid, yr)]
n_filled += 1
end
elseif haskey(constituent_to_union, pid)
# Fallback: use union mapping even if union_party_id column not populated
uid = constituent_to_union[pid]
if haskey(election_lookup, (uid, yr))
output.pervote[i] = election_lookup[(uid, yr)]
n_filled += 1
end
end
end
println(" pervote: $n_filled / $(nrow(output)) rows filled")
else
println(" WARNING: election_data.csv not found")
end
# --- Reorder columns ---
estimate_cols = Symbol[]
for base in ["economic_lr", "galtan", "pro_market", "pro_welfare", "cosmopolitan", "traditional"]
sym = Symbol(base)
if hasproperty(output, sym)
push!(estimate_cols, sym)
push!(estimate_cols, Symbol("$(base)_se"))
push!(estimate_cols, Symbol("$(base)_q025"))
push!(estimate_cols, Symbol("$(base)_q975"))
end
end
# Fix country code: MO (Macau) → MK (North Macedonia) — GPS uses wrong ISO2
output.country = replace(output.country, "MO" => "MK")
col_order = vcat(
[:party_id, :party_name_english, :party_name_short, :country, :year, :segment_num,
:union_party_id, :in_union, :pervote],
estimate_cols
)
col_order = filter(c -> hasproperty(output, c), col_order)
select!(output, col_order)
# --- Write back ---
CSV.write(input_file, output)
println("\n Wrote: $input_file")
println(" Columns ($(ncol(output))): $(join(string.(names(output)), ", "))")
return output
end
function main(args=ARGS)
input = length(args) >= 1 ? args[1] : find_latest_output()
enrich(input)
println("\nDone.")
end
if abspath(PROGRAM_FILE) == @__FILE__
main()
end
+985
View File
@@ -0,0 +1,985 @@
#!/usr/bin/env julia
#=
02_post_estimation.jl - Extract party position estimates from Stan model output
Supports both model versions:
- 2D model (V1): Extracts economic_lr and galtan directly
- 4D model (V10): Extracts 4 traits + 2 derived scales
V10/V1 UPDATE: Handles segment-based indexing
- Maps segment results back to original party IDs
- Adds segment_num column to indicate which segment of the party
- Flags discontinuities for parties with multiple segments
This script:
1. Auto-detects the latest model run in model_outputs/
2. Loads the chain CSV files and data mappings
3. Detects model version from metadata or column names
4. Extracts posterior summaries for all segment-year positions
5. Maps Stan parameter indices back to real party IDs, segment numbers, and years
6. Saves output as wide-format CSV with uncertainty estimates
Usage:
julia 02_post_estimation.jl
Output:
party_positions_YYYY-MM-DD_HH-MM-SS.csv
=#
using CSV
using DataFrames
using Statistics
using JSON
using Dates
using Printf
# =============================================================================
# STEP 0: Auto-detect latest run
# =============================================================================
function find_latest_run(base_dir::String="outputs/model_outputs/latest")
if !isdir(base_dir)
error("Model outputs directory not found: $base_dir")
end
runs = filter(d -> startswith(d, "run_") && isdir(joinpath(base_dir, d)), readdir(base_dir))
if isempty(runs)
error("No runs found in $base_dir")
end
# Sort by timestamp in directory name (format: run_YYYY-MM-DD_HH-MM-SS)
sort!(runs, rev=true)
latest = joinpath(base_dir, runs[1])
println("Found $(length(runs)) run(s). Using latest: $latest")
return latest
end
# =============================================================================
# STEP 1: Load data and build segment-year lookup
# =============================================================================
function load_run_data(run_dir::String)
println("\n" * "="^60)
println("LOADING RUN DATA")
println("="^60)
data_dir = joinpath(run_dir, "data")
chains_dir = joinpath(run_dir, "chains")
# Check required files exist
required_files = [
joinpath(data_dir, "text_data.csv"),
joinpath(data_dir, "expert_dim.csv"),
joinpath(data_dir, "expert_lr.csv"),
joinpath(run_dir, "metadata.json")
]
for f in required_files
if !isfile(f)
error("Required file not found: $f")
end
end
# Load data files
println("Loading text_data.csv...")
text_data = CSV.read(joinpath(data_dir, "text_data.csv"), DataFrame)
println(" Rows: $(nrow(text_data))")
println("Loading expert_dim.csv...")
expert_dim = CSV.read(joinpath(data_dir, "expert_dim.csv"), DataFrame)
println(" Rows: $(nrow(expert_dim))")
println("Loading expert_lr.csv...")
expert_lr = CSV.read(joinpath(data_dir, "expert_lr.csv"), DataFrame)
println(" Rows: $(nrow(expert_lr))")
println("Loading metadata.json...")
metadata = JSON.parsefile(joinpath(run_dir, "metadata.json"))
println(" year0: $(metadata["year0"])")
println(" Model: $(metadata["model_file"])")
# V10: Load segment_info if available
segment_info_file = joinpath(data_dir, "segment_info.csv")
segment_info = nothing
if isfile(segment_info_file)
println("Loading segment_info.csv (V10)...")
segment_info = CSV.read(segment_info_file, DataFrame)
println(" Segments: $(nrow(segment_info))")
end
# V10: Load segment_year_map if available
segment_year_file = joinpath(data_dir, "segment_year_map.csv")
segment_year_map = nothing
if isfile(segment_year_file)
println("Loading segment_year_map.csv (V10)...")
segment_year_map = CSV.read(segment_year_file, DataFrame)
println(" Segment-years: $(nrow(segment_year_map))")
end
# Find chain files
chain_files = filter(f -> endswith(f, ".csv") && startswith(f, "chain_"), readdir(chains_dir))
println("\nFound $(length(chain_files)) chain file(s)")
return (
text_data = text_data,
expert_dim = expert_dim,
expert_lr = expert_lr,
metadata = metadata,
segment_info = segment_info,
segment_year_map = segment_year_map,
chain_files = [joinpath(chains_dir, f) for f in sort(chain_files)],
run_dir = run_dir
)
end
function normalize_country_value(value)
if ismissing(value)
return missing
end
txt = strip(string(value))
return isempty(txt) ? missing : txt
end
function build_party_country_map(text_data::DataFrame, expert_dim::DataFrame, expert_lr::DataFrame)
merged = unique(vcat(
select(text_data, :party, :country),
select(expert_dim, :party, :country),
select(expert_lr, :party, :country)
))
party_to_country = Dict{Int, String}()
for row in eachrow(merged)
pid = tryparse(Int, string(row.party))
if pid === nothing
continue
end
c = normalize_country_value(row.country)
if !ismissing(c)
party_to_country[pid] = c
end
end
return party_to_country
end
function load_constituent_to_union_map()::Dict{Int, Int}
mapping_file = joinpath("data", "union_mapping.csv")
constituent_to_union = Dict{Int, Int}()
if isfile(mapping_file)
union_df = CSV.read(mapping_file, DataFrame)
for row in eachrow(union_df)
constituent_to_union[row.expert_pf_id] = row.manifesto_pf_id
end
end
return constituent_to_union
end
function resolve_party_country(pid_value,
party_to_country::Dict{Int, String},
constituent_to_union::Dict{Int, Int})
pid = tryparse(Int, string(pid_value))
if pid === nothing
return missing, "unresolved"
end
if haskey(party_to_country, pid)
return party_to_country[pid], "direct"
end
if haskey(constituent_to_union, pid)
uid = constituent_to_union[pid]
if haskey(party_to_country, uid)
return party_to_country[uid], "union_fallback"
end
end
return missing, "unresolved"
end
function apply_country_resolution!(df::DataFrame,
party_col::Symbol,
country_col::Symbol,
party_to_country::Dict{Int, String},
constituent_to_union::Dict{Int, Int})
resolved_country = Union{Missing, String}[]
source_counts = Dict("direct" => 0, "union_fallback" => 0, "unresolved" => 0)
unresolved_parties = Set{Int}()
for pid in df[!, party_col]
country, source = resolve_party_country(pid, party_to_country, constituent_to_union)
push!(resolved_country, country)
source_counts[source] += 1
if source == "unresolved"
pid_int = tryparse(Int, string(pid))
if pid_int !== nothing
push!(unresolved_parties, pid_int)
end
end
end
df[!, country_col] = resolved_country
return source_counts, sort!(collect(unresolved_parties))
end
function fill_missing_countries!(df::DataFrame,
segment_info::Union{DataFrame, Nothing},
party_to_country::Dict{Int, String},
constituent_to_union::Dict{Int, Int})
if !hasproperty(df, :country)
source_counts, unresolved = apply_country_resolution!(
df, :party_id, :country, party_to_country, constituent_to_union
)
return source_counts, unresolved
end
normalized = Union{Missing, String}[]
for val in df.country
push!(normalized, normalize_country_value(val))
end
df.country = normalized
source_counts = Dict("direct" => 0, "union_fallback" => 0, "segment_info" => 0, "unresolved" => 0)
segment_country_by_id = Dict{Int, String}()
if segment_info !== nothing && hasproperty(segment_info, :country)
for row in eachrow(segment_info)
c = normalize_country_value(row.country)
if !ismissing(c)
segment_country_by_id[Int(row.segment_id)] = c
end
end
end
unresolved_parties = Set{Int}()
for i in 1:nrow(df)
if !ismissing(df.country[i])
continue
end
if hasproperty(df, :segment_id) && haskey(segment_country_by_id, Int(df.segment_id[i]))
df.country[i] = segment_country_by_id[Int(df.segment_id[i])]
source_counts["segment_info"] += 1
continue
end
country, source = resolve_party_country(df.party_id[i], party_to_country, constituent_to_union)
if !ismissing(country)
df.country[i] = country
source_counts[source] += 1
else
source_counts["unresolved"] += 1
pid_int = tryparse(Int, string(df.party_id[i]))
pid_int !== nothing && push!(unresolved_parties, pid_int)
end
end
return source_counts, sort!(collect(unresolved_parties))
end
# =============================================================================
# STEP 2: Build complete segment-year mapping (V10) or party-year mapping (V9)
# =============================================================================
function build_segment_year_map(text_data::DataFrame, expert_dim::DataFrame, expert_lr::DataFrame,
segment_info::Union{DataFrame, Nothing},
segment_year_map::Union{DataFrame, Nothing},
run_dir::String,
year0::Int)
println("\n" * "="^60)
println("BUILDING SEGMENT-YEAR MAPPING")
println("="^60)
party_to_country = build_party_country_map(text_data, expert_dim, expert_lr)
constituent_to_union = load_constituent_to_union_map()
# V10: Use segment_year_map if available
if segment_year_map !== nothing && segment_info !== nothing
println("Using segment_year_map.csv (V10 mode)")
# Convert relative Year to absolute year
if hasproperty(segment_year_map, :Year)
segment_year_map.year = segment_year_map.Year .+ year0
elseif !hasproperty(segment_year_map, :year)
error("segment_year_map has no Year or year column")
end
# Add party_id from segment_info if not already present
if !hasproperty(segment_year_map, :party_id)
segment_id_to_party = Dict(row.segment_id => row.party_id for row in eachrow(segment_info))
segment_year_map.party_id = [segment_id_to_party[sid] for sid in segment_year_map.segment_id]
end
# Add segment_num from segment_info if not already present
if !hasproperty(segment_year_map, :segment_num)
segment_id_to_segnum = Dict(row.segment_id => row.segment_num for row in eachrow(segment_info))
segment_year_map.segment_num = [segment_id_to_segnum[sid] for sid in segment_year_map.segment_id]
end
# Resolve/fill country column using segment metadata first, then direct and union-fallback lookup.
source_counts, unresolved = fill_missing_countries!(
segment_year_map, segment_info, party_to_country, constituent_to_union
)
direct_count = get(source_counts, "direct", 0)
union_count = get(source_counts, "union_fallback", 0)
segment_info_count = get(source_counts, "segment_info", 0)
unresolved_count = count(ismissing, segment_year_map.country)
println(" Country resolution fill counts: direct=$direct_count, union_fallback=$union_count, segment_info=$segment_info_count, unresolved_rows=$unresolved_count")
if !isempty(unresolved)
println(" Warning: unresolved country party IDs (first 20): $(unresolved[1:min(20, length(unresolved))])")
end
R = maximum(segment_year_map.rr)
n_segments = length(unique(segment_year_map.segment_id))
n_parties = length(unique(segment_year_map.party_id))
println("Loaded segment_year_map: $(nrow(segment_year_map)) segment-years (R=$R)")
println(" Unique segments: $n_segments")
println(" Unique parties: $n_parties")
# Count observed vs interpolated
observed_rrs = Set{Int}()
if hasproperty(text_data, :rr_man)
union!(observed_rrs, Set(text_data.rr_man))
end
if hasproperty(expert_dim, :rr_exp_dim)
union!(observed_rrs, Set(expert_dim.rr_exp_dim))
end
if hasproperty(expert_lr, :rr_exp_lr)
union!(observed_rrs, Set(expert_lr.rr_exp_lr))
end
n_observed = length(intersect(Set(segment_year_map.rr), observed_rrs))
n_interpolated = nrow(segment_year_map) - n_observed
println(" Observed segment-years: $n_observed")
println(" Interpolated segment-years: $n_interpolated")
return segment_year_map, R, segment_info
end
# V9 fallback: Use party_year_map
party_year_file = joinpath(run_dir, "data", "party_year_map.csv")
if isfile(party_year_file)
println("Loading party_year_map.csv (V9 fallback mode)")
party_year_map = CSV.read(party_year_file, DataFrame)
# Add party_id column (same as party for V9)
if !hasproperty(party_year_map, :party_id)
party_year_map.party_id = party_year_map.party
end
# Add segment_num column (always 1 for V9)
if !hasproperty(party_year_map, :segment_num)
party_year_map.segment_num = ones(Int, nrow(party_year_map))
end
# Add segment_id column (same as party index for V9)
if !hasproperty(party_year_map, :segment_id)
party_year_map.segment_id = party_year_map.party
end
# Convert relative Year to absolute year
if hasproperty(party_year_map, :Year)
party_year_map.year = party_year_map.Year .+ year0
elseif !hasproperty(party_year_map, :year)
error("party_year_map has no Year or year column")
end
# Resolve/fill country column
if !hasproperty(party_year_map, :country)
source_counts, unresolved = apply_country_resolution!(
party_year_map, :party_id, :country, party_to_country, constituent_to_union
)
direct_count = source_counts["direct"]
union_count = source_counts["union_fallback"]
unresolved_count = source_counts["unresolved"]
println(" Country resolution sources: direct=$direct_count, union_fallback=$union_count, unresolved=$unresolved_count")
if !isempty(unresolved)
println(" Warning: unresolved country party IDs (first 20): $(unresolved[1:min(20, length(unresolved))])")
end
else
normalized = Union{Missing, String}[]
for val in party_year_map.country
push!(normalized, normalize_country_value(val))
end
party_year_map.country = normalized
end
R = maximum(party_year_map.rr)
println("Loaded party_year_map: $(nrow(party_year_map)) party-years (R=$R)")
return party_year_map, R, nothing
end
# Fallback: Reconstruct from data files
@warn "No mapping file found, reconstructing from data (observed years only)"
# Extract unique party-year-rr combinations from text_data
text_map = unique(select(text_data, :party, :country, :year, :rr_man))
rename!(text_map, :rr_man => :rr)
text_map.party_id = text_map.party
text_map.segment_num = ones(Int, nrow(text_map))
expert_dim_map = unique(select(expert_dim, :party, :country, :year, :rr_exp_dim))
rename!(expert_dim_map, :rr_exp_dim => :rr)
expert_dim_map.party_id = expert_dim_map.party
expert_dim_map.segment_num = ones(Int, nrow(expert_dim_map))
expert_lr_map = unique(select(expert_lr, :party, :country, :year, :rr_exp_lr))
rename!(expert_lr_map, :rr_exp_lr => :rr)
expert_lr_map.party_id = expert_lr_map.party
expert_lr_map.segment_num = ones(Int, nrow(expert_lr_map))
combined = vcat(text_map, expert_dim_map, expert_lr_map)
segment_year_map = unique(combined)
sort!(segment_year_map, :rr)
R = maximum(segment_year_map.rr)
println("Reconstructed mapping: $(nrow(segment_year_map)) segment-years (R=$R)")
return segment_year_map, R, nothing
end
# =============================================================================
# STEP 3: Load and combine chains
# =============================================================================
function load_chains(chain_files::Vector{String})
println("\n" * "="^60)
println("LOADING STAN CHAINS")
println("="^60)
flush(stdout)
chains = DataFrame[]
# The full Stan CSVs are very wide (hundreds of thousands of columns). For
# post-estimation we only need party-position generated quantities. Reading
# all columns can take hours and allocate many GB of irrelevant parameters.
post_estimation_prefixes = (
"economic_lr.",
"galtan.",
"pro_market.",
"pro_welfare.",
"cosmopolitan.",
"traditional.",
)
keep_post_estimation_col(_i, name) = any(startswith(String(name), p) for p in post_estimation_prefixes)
for (i, f) in enumerate(chain_files)
println("Loading chain $i: $(basename(f))...")
flush(stdout)
# Skip comment lines (Stan header) and parse only needed quantities.
chain = CSV.read(f, DataFrame; comment="#", select=keep_post_estimation_col)
println(" Samples: $(nrow(chain)), Parameters: $(ncol(chain))")
flush(stdout)
push!(chains, chain)
end
# Combine chains
println("Combining selected chain columns...")
flush(stdout)
combined = vcat(chains...)
println("\nCombined: $(nrow(combined)) total samples")
println("Selected parameters: $(ncol(combined))")
flush(stdout)
return combined
end
# =============================================================================
# STEP 4: Extract generated quantities
# =============================================================================
"""
Detect model version from chain column names.
Returns "2dim" or "4dim".
"""
function detect_model_version(chains::DataFrame)
cols = names(chains)
# 2D model has economic_lr but NOT pro_market
has_economic_lr = any(c -> startswith(string(c), "economic_lr."), cols)
has_pro_market = any(c -> startswith(string(c), "pro_market."), cols)
if has_economic_lr && !has_pro_market
return "2dim"
elseif has_pro_market
return "4dim"
else
error("Could not detect model version from chain columns")
end
end
function extract_estimates(chains::DataFrame, segment_year_map::DataFrame, R::Int)
println("\n" * "="^60)
println("EXTRACTING POSTERIOR ESTIMATES")
println("="^60)
# Auto-detect model version from columns
model_version = detect_model_version(chains)
println("Detected model version: $model_version")
# Select quantities based on model version
if model_version == "2dim"
# 2D model: economic_lr and galtan are directly estimated
# (general_lr is computed in Stan for anchoring but not extracted as output)
quantities = ["economic_lr", "galtan"]
test_col = "economic_lr.1"
else
# 4D model: 4 traits + 2 derived scales
quantities = ["pro_market", "pro_welfare", "cosmopolitan", "traditional", "economic_lr", "galtan"]
test_col = "pro_market.1"
end
# Check that columns exist
if !hasproperty(chains, Symbol(test_col))
error("Column $test_col not found in chains. Available columns: $(first(names(chains), 10))...")
end
n_samples = nrow(chains)
println("Samples per parameter: $n_samples")
# Load union mapping for adding union_party_id column
union_mapping_file = joinpath("data", "union_mapping.csv")
constituent_to_union_pf = Dict{Int, Int}()
if isfile(union_mapping_file)
union_df = CSV.read(union_mapping_file, DataFrame)
for row in eachrow(union_df)
constituent_to_union_pf[row.expert_pf_id] = row.manifesto_pf_id
end
end
# Pre-allocate output DataFrame
n_rows = nrow(segment_year_map)
# Add union_party_id column: NA for standalone parties, union PF ID for constituents
union_ids = Union{Int, Missing}[]
for pid in segment_year_map.party_id
pid_int = isa(pid, Integer) ? pid : tryparse(Int, string(pid))
if pid_int !== nothing && haskey(constituent_to_union_pf, pid_int)
push!(union_ids, constituent_to_union_pf[pid_int])
else
push!(union_ids, missing)
end
end
output = DataFrame(
party_id = segment_year_map.party_id,
union_party_id = union_ids,
segment_num = segment_year_map.segment_num,
country = segment_year_map.country,
year = segment_year_map.year,
rr = segment_year_map.rr
)
# Add columns for each quantity
for q in quantities
output[!, Symbol(q)] = zeros(Float64, n_rows)
output[!, Symbol("$(q)_se")] = zeros(Float64, n_rows)
output[!, Symbol("$(q)_q025")] = zeros(Float64, n_rows)
output[!, Symbol("$(q)_q975")] = zeros(Float64, n_rows)
end
println("Extracting estimates for $(n_rows) segment-year positions...")
# Progress tracking
prog_interval = max(1, n_rows ÷ 20)
for (i, row) in enumerate(eachrow(segment_year_map))
r = row.rr
# Progress
if i % prog_interval == 0 || i == n_rows
pct = round(100 * i / n_rows, digits=1)
print("\r Progress: $pct% ($i / $n_rows)")
end
for q in quantities
col_name = Symbol("$q.$r")
if !hasproperty(chains, col_name)
@warn "Column $col_name not found (rr=$r)" maxlog=5
continue
end
samples = chains[!, col_name]
# Compute summary statistics
output[i, Symbol(q)] = mean(samples)
output[i, Symbol("$(q)_se")] = std(samples)
output[i, Symbol("$(q)_q025")] = quantile(samples, 0.025)
output[i, Symbol("$(q)_q975")] = quantile(samples, 0.975)
end
end
println() # Newline after progress
# Remove the rr column from final output (internal only)
select!(output, Not(:rr))
return output
end
# =============================================================================
# STEP 5: Validation
# =============================================================================
function validate_output(output::DataFrame, segment_info::Union{DataFrame, Nothing})
println("\n" * "="^60)
println("VALIDATION CHECKS")
println("="^60)
all_passed = true
# Detect which columns are present (2D vs 4D model)
has_4d = hasproperty(output, :pro_market)
# Check 1: Range check - all estimates should be in [0, 1]
println("\n1. Range check (all values in [0, 1]):")
if has_4d
check_cols = [:pro_market, :pro_welfare, :cosmopolitan, :traditional, :economic_lr, :galtan]
else
check_cols = [:economic_lr, :galtan]
end
for col in check_cols
if !hasproperty(output, col)
continue
end
vals = output[!, col]
min_val, max_val = extrema(vals)
in_range = min_val >= 0 && max_val <= 1
status = in_range ? "PASS" : "FAIL"
println(" $col: [$(@sprintf("%.4f", min_val)), $(@sprintf("%.4f", max_val))] - $status")
all_passed = all_passed && in_range
end
# Check 2: Anchor party checks
println("\n2. Anchor party checks:")
# Define anchor parties with expected ranges (for 2D model)
# Includes both union IDs (V3) and individual constituent IDs (V4)
anchor_parties = [
(id=211, name="CDU/CSU", country="DE", econ=(0.50, 0.70), galtan=(0.45, 0.70)),
(id=1375, name="CDU", country="DE", econ=(0.50, 0.70), galtan=(0.45, 0.65)),
(id=1731, name="CSU", country="DE", econ=(0.50, 0.70), galtan=(0.55, 0.75)),
(id=383, name="SPD", country="DE", econ=(0.30, 0.50), galtan=(0.30, 0.55)),
(id=1516, name="Labour", country="GB", econ=(0.30, 0.55), galtan=(0.30, 0.55)),
(id=1567, name="Conservatives", country="GB", econ=(0.55, 0.80), galtan=(0.50, 0.75)),
(id=487, name="SAP", country="SE", econ=(0.30, 0.50), galtan=(0.35, 0.55)),
]
n_checked = 0
n_passed = 0
for anchor in anchor_parties
party_rows = filter(r -> r.party_id == anchor.id, output)
if nrow(party_rows) == 0
println(" $(anchor.name) ($(anchor.id)): NOT FOUND")
continue
end
# Use most recent 20 years of data as reference period
max_year = maximum(party_rows.year)
ref_rows = filter(r -> r.year >= max_year - 20, party_rows)
if nrow(ref_rows) == 0
ref_rows = party_rows
end
n_checked += 1
mean_econ = mean(ref_rows.economic_lr)
mean_galtan = mean(ref_rows.galtan)
econ_ok = anchor.econ[1] <= mean_econ <= anchor.econ[2]
galtan_ok = anchor.galtan[1] <= mean_galtan <= anchor.galtan[2]
all_ok = econ_ok && galtan_ok
if all_ok
n_passed += 1
end
status = all_ok ? "PASS" : "WARN"
econ_marker = econ_ok ? "" : "*"
galtan_marker = galtan_ok ? "" : "*"
@printf(" %-15s econ=%.2f%s [%.2f-%.2f] galtan=%.2f%s [%.2f-%.2f] %s\n",
anchor.name, mean_econ, econ_marker, anchor.econ[1], anchor.econ[2],
mean_galtan, galtan_marker, anchor.galtan[1], anchor.galtan[2], status)
end
if n_checked > 0
println(" Anchor check: $n_passed/$n_checked within expected ranges")
println(" Note: Model integrates text + expert data; deviations from expert-only expectations are normal")
end
# Check 3: Coverage check
println("\n3. Coverage check:")
println(" Total segment-year positions: $(nrow(output))")
println(" Unique parties: $(length(unique(output.party_id)))")
blank_country_rows = count(ismissing, output.country)
println(" Unique countries: $(length(unique(skipmissing(output.country))))")
if blank_country_rows == 0
println(" Blank country rows: 0 - PASS")
else
println(" Blank country rows: $blank_country_rows - FAIL")
all_passed = false
end
println(" Year range: $(minimum(output.year)) - $(maximum(output.year))")
# V10: Check segment distribution
segment_counts = combine(groupby(output, :party_id), nrow => :n_years)
parties_multi_segment = filter(r -> r.party_id in
[p for p in unique(output.party_id) if length(unique(filter(x -> x.party_id == p, output).segment_num)) > 1],
segment_counts)
if nrow(parties_multi_segment) > 0
println("\n Parties with multiple segments: $(length(unique(parties_multi_segment.party_id)))")
end
# Check 4: No duplicates (party_id, segment_num, year should be unique)
println("\n4. Duplicate check:")
dup_count = nrow(output) - nrow(unique(select(output, :party_id, :segment_num, :year)))
if dup_count == 0
println(" No duplicate (party_id, segment_num, year) combinations - PASS")
else
println(" WARNING: Found $dup_count duplicate combinations!")
all_passed = false
end
# Check 5: SE reasonableness
println("\n5. Standard error check:")
se_cols = has_4d ?
[:pro_market_se, :pro_welfare_se, :cosmopolitan_se, :traditional_se] :
[:economic_lr_se, :galtan_se]
for col in se_cols
if !hasproperty(output, col)
continue
end
vals = output[!, col]
mean_se = mean(vals)
max_se = maximum(vals)
println(" $col: mean=$(@sprintf("%.4f", mean_se)), max=$(@sprintf("%.4f", max_se))")
end
println("\n" * "-"^60)
if all_passed
println("All validation checks PASSED")
else
println("Some validation checks FAILED - please review output carefully")
end
return all_passed
end
# =============================================================================
# STEP 6: Save output
# =============================================================================
function save_output(output::DataFrame, metadata::Dict, segment_info::Union{DataFrame, Nothing}, run_dir::String; outdir::String="outputs/estimations/latest")
println("\n" * "="^60)
println("SAVING OUTPUT")
println("="^60)
timestamp = Dates.format(now(), "yyyy-mm-dd_HH-MM-SS")
mkpath(outdir)
# Delete previous output files
for f in readdir(outdir)
if startswith(f, "party_positions_") && (endswith(f, ".csv") || endswith(f, ".txt") || endswith(f, ".tex"))
rm(joinpath(outdir, f))
println(" Deleted old: $f")
end
end
# Save main CSV
csv_file = joinpath(outdir, "party_positions_$timestamp.csv")
CSV.write(csv_file, output)
println("Saved: $csv_file")
println(" Rows: $(nrow(output))")
println(" Columns: $(ncol(output))")
# Count parties with multiple segments
n_multi_segment = 0
if segment_info !== nothing
party_segment_counts = combine(groupby(segment_info, :party_id), nrow => :n_segments)
n_multi_segment = count(r -> r.n_segments > 1, eachrow(party_segment_counts))
end
# Save metadata
meta_file = joinpath(outdir, "party_positions_$(timestamp)_metadata.txt")
open(meta_file, "w") do f
println(f, "Party Positions Dataset - Metadata")
println(f, "="^50)
println(f, "")
println(f, "Generated: $(Dates.format(now(), "yyyy-mm-dd HH:MM:SS"))")
println(f, "Source run: $(basename(run_dir))")
println(f, "Model file: $(get(metadata, "model_file", "unknown"))")
println(f, "")
println(f, "Dataset size:")
println(f, " Segment-year observations: $(nrow(output))")
println(f, " Unique parties: $(length(unique(output.party_id)))")
if n_multi_segment > 0
println(f, " Parties with multiple segments: $n_multi_segment")
end
println(f, " Unique countries: $(length(unique(output.country)))")
println(f, " Year range: $(minimum(output.year)) - $(maximum(output.year))")
println(f, "")
println(f, "Columns:")
println(f, " party_id: PartyFacts ID (integer) - individual party (e.g., CDU=1375, CSU=1731)")
println(f, " union_party_id: PartyFacts ID of parent union (NA for standalone parties)")
println(f, " segment_num: Segment number within party (1, 2, 3...)")
println(f, " country: ISO2 country code")
println(f, " year: Calendar year")
println(f, "")
println(f, "Segment-Based Indexing:")
println(f, " - Parties are split into segments at gaps > 7 years")
println(f, " - Each segment is estimated independently (no continuity across gaps)")
println(f, " - Segments with < 3 observations are dropped")
println(f, " - segment_num=1 is the main segment; higher numbers indicate gaps in data")
println(f, "")
# Check if this is 2D or 4D output
is_2d = !hasproperty(output, :pro_market)
if is_2d
println(f, "Model: 2D Direct Bipolar")
println(f, "")
println(f, "Bipolar scales (0 = left/GAL, 1 = right/TAN):")
println(f, " economic_lr: Economic left-right position (directly estimated)")
println(f, " galtan: GAL-TAN cultural position (directly estimated)")
# Note: general_lr is computed internally for cross-dimensional anchoring
# but not reported as output (the two dimension-specific estimates are preferred)
else
println(f, "Model: 4D Unipolar")
println(f, "")
println(f, "Dimensions (0 = low, 1 = high):")
println(f, " pro_market: Pro-market economic position")
println(f, " pro_welfare: Pro-welfare state position")
println(f, " cosmopolitan: Cosmopolitan/GAL cultural position")
println(f, " traditional: Traditional/TAN cultural position")
println(f, "")
println(f, "Derived bipolar scales (0 = left/GAL, 1 = right/TAN):")
println(f, " economic_lr: Economic left-right (derived from pro_market - pro_welfare)")
println(f, " galtan: GAL-TAN (derived from traditional - cosmopolitan)")
end
println(f, "")
println(f, "Uncertainty columns:")
println(f, " *_se: Standard error (posterior SD)")
println(f, " *_q025: 2.5th percentile (lower 95% CI)")
println(f, " *_q975: 97.5th percentile (upper 95% CI)")
println(f, "")
println(f, "Model convergence:")
println(f, " Mean R-hat: $(get(metadata, "mean_rhat", "N/A"))")
println(f, " Max R-hat: $(get(metadata, "max_rhat", "N/A"))")
println(f, " Mean ESS: $(get(metadata, "mean_ess", "N/A"))")
println(f, " Min ESS: $(get(metadata, "min_ess", "N/A"))")
end
println("Saved: $meta_file")
# =============================================================================
# STEP 5b: Verify no union/alliance IDs in output
# =============================================================================
function verify_no_unions_in_output(output::DataFrame)
println("\n" * "="^60)
println("UNION ID VERIFICATION")
println("="^60)
union_mapping_file = joinpath("data", "union_mapping.csv")
if !isfile(union_mapping_file)
println(" No union_mapping.csv found — skipping verification")
return
end
union_df = CSV.read(union_mapping_file, DataFrame)
union_pf_ids = Set(union_df.manifesto_pf_id)
output_pf_ids = Set(output.party_id)
violations = intersect(union_pf_ids, output_pf_ids)
if isempty(violations)
println(" PASS: No union/alliance PF IDs found in output")
println(" Checked $(length(union_pf_ids)) union IDs against $(length(output_pf_ids)) output parties")
else
println(" WARNING: $(length(violations)) union PF IDs found in output")
println(" (This is expected if union_mapping.csv was updated after the model run)")
for v in sort(collect(violations))
n_rows = count(r -> r.party_id == v, eachrow(output))
println(" PF $v: $n_rows rows")
end
end
end
# =============================================================================
# MAIN
# =============================================================================
function main()
println("="^60)
println("POST-ESTIMATION: 4D Latent Trait Model (V10)")
println("="^60)
println("Started: $(Dates.format(now(), "yyyy-mm-dd HH:MM:SS"))")
# Step 0: Find run directory (CLI --run-dir or auto-detect latest)
run_dir = nothing
output_dir = nothing
for (i, arg) in enumerate(ARGS)
if arg == "--run-dir" && i < length(ARGS)
run_dir = ARGS[i + 1]
elseif startswith(arg, "--run-dir=")
run_dir = split(arg, "=", limit=2)[2]
elseif arg == "--output-dir" && i < length(ARGS)
output_dir = ARGS[i + 1]
elseif startswith(arg, "--output-dir=")
output_dir = split(arg, "=", limit=2)[2]
end
end
if run_dir === nothing
run_dir = find_latest_run()
else
println("Using specified run directory: $run_dir")
end
# Step 1: Load run data
data = load_run_data(run_dir)
# Step 2: Build segment-year mapping
year0 = data.metadata["year0"]
segment_year_map, R, segment_info = build_segment_year_map(
data.text_data, data.expert_dim, data.expert_lr,
data.segment_info, data.segment_year_map, data.run_dir, year0
)
# Step 3: Load chains
chains = load_chains(data.chain_files)
# Step 4: Extract estimates
output = extract_estimates(chains, segment_year_map, R)
# Step 5: Validate
validate_output(output, segment_info)
# Step 5b: Verify no union/alliance IDs in output
verify_no_unions_in_output(output)
# Step 6: Save output
effective_output_dir = output_dir !== nothing ? output_dir : "outputs/estimations/latest"
csv_file, meta_file = save_output(output, data.metadata, segment_info, run_dir; outdir=effective_output_dir)
println("\n" * "="^60)
println("COMPLETE")
println("="^60)
println("Output files:")
println(" $csv_file")
println(" $meta_file")
println("\nFinished: $(Dates.format(now(), "yyyy-mm-dd HH:MM:SS"))")
return output
end
# Run if executed directly
if abspath(PROGRAM_FILE) == @__FILE__
main()
end
+332
View File
@@ -0,0 +1,332 @@
#!/usr/bin/env julia
#############################################################################
## 00_validation.jl
## Pre-flight validation checks for Stan data and initialization
## Prevents cryptic Stan errors by catching issues early
#############################################################################
using Statistics
"""
Validate Stan data dictionary before passing to Stan.
Catches common issues that cause Stan to crash with cryptic errors.
"""
function validate_stan_data(dat::Dict; verbose=true)
verbose && println("\n" * "="^70)
verbose && println("VALIDATING STAN DATA")
verbose && println("="^70)
errors = String[]
warnings = String[]
# Check for NaN/Inf in all numeric data
for (key, value) in dat
if isa(value, AbstractArray) && eltype(value) <: Number
if any(isnan, value)
push!(errors, "Data '$key' contains NaN values")
end
if any(isinf, value)
push!(errors, "Data '$key' contains Inf values")
end
elseif isa(value, Number)
if isnan(value)
push!(errors, "Data '$key' is NaN")
end
if isinf(value)
push!(errors, "Data '$key' is Inf")
end
end
end
# Validate expert data is in open interval (0, 1)
if haskey(dat, "val_dim")
val_dim = dat["val_dim"]
if any(x -> x <= 0 || x >= 1, val_dim)
n_boundary = count(x -> x <= 0 || x >= 1, val_dim)
push!(errors, "Expert dimension data has $n_boundary values at boundaries (must be in (0,1))")
if verbose
println(" Dimension-specific expert data range: [$(minimum(val_dim)), $(maximum(val_dim))]")
end
end
end
if haskey(dat, "val_lr")
val_lr = dat["val_lr"]
if any(x -> x <= 0 || x >= 1, val_lr)
n_boundary = count(x -> x <= 0 || x >= 1, val_lr)
push!(errors, "Expert L-R data has $n_boundary values at boundaries (must be in (0,1))")
if verbose
println(" L-R expert data range: [$(minimum(val_lr)), $(maximum(val_lr))]")
end
end
end
# Validate manifesto data
if haskey(dat, "positive") && haskey(dat, "sample")
positive = dat["positive"]
sample = dat["sample"]
if any(positive .> sample)
push!(errors, "Manifesto: positive counts exceed sample sizes")
end
if any(positive .< 0)
push!(errors, "Manifesto: negative positive counts found")
end
if any(sample .< 0)
push!(errors, "Manifesto: negative sample sizes found")
end
end
# Validate indices are within bounds
# V10: Check segment indices (ss_man) if present, otherwise party indices (jj_man)
if haskey(dat, "S") && haskey(dat, "ss_man")
S = dat["S"]
ss_man = dat["ss_man"]
if any(ss_man .< 1) || any(ss_man .> S)
push!(errors, "Manifesto segment indices out of bounds [1, $S]")
end
elseif haskey(dat, "J") && haskey(dat, "jj_man")
J = dat["J"]
jj_man = dat["jj_man"]
if any(jj_man .< 1) || any(jj_man .> J)
push!(errors, "Manifesto party indices out of bounds [1, $J]")
end
end
if haskey(dat, "R") && haskey(dat, "rr_man")
R = dat["R"]
rr_man = dat["rr_man"]
if any(rr_man .< 1) || any(rr_man .> R)
push!(errors, "Manifesto party-year indices out of bounds [1, $R]")
end
end
# V4: Validate constituent arrays
if haskey(dat, "const_rr_man") && haskey(dat, "R")
R = dat["R"]
const_rr = dat["const_rr_man"]
if any(const_rr .< 1) || any(const_rr .> R)
push!(errors, "const_rr_man out of bounds [1, $R]")
end
# Verify offsets are valid
if haskey(dat, "const_offset_man") && haskey(dat, "n_const_man")
offsets = dat["const_offset_man"]
n_consts = dat["n_const_man"]
total = dat["N_const_man_total"]
for i in eachindex(offsets)
if offsets[i] + n_consts[i] - 1 > total
push!(errors, "const_offset_man[$i] + n_const_man[$i] exceeds N_const_man_total")
break
end
end
end
end
# Print summary
if verbose
println("\nDATA SUMMARY:")
# V10: Show segments if present
if haskey(dat, "S")
println(" Segments (S): $(dat["S"])")
println(" Parties with segments (J): $(get(dat, "J", "N/A"))")
else
println(" Parties (J): $(get(dat, "J", "N/A"))")
end
println(" Countries (P): $(get(dat, "P", "N/A"))")
println(" Segment-years (R): $(get(dat, "R", "N/A"))")
println(" Years (T_year): $(get(dat, "T_year", "N/A"))")
println(" Manifesto obs: $(get(dat, "N_man", "N/A"))")
println(" Expert dim obs: $(get(dat, "N_exp_dim", "N/A"))")
println(" Expert L-R obs: $(get(dat, "N_exp_lr", "N/A"))")
if haskey(dat, "mn_resp_log_man")
println("\nPRIOR MEANS:")
println(" Manifesto: $(round(dat["mn_resp_log_man"], digits=3))")
println(" Expert dim: $(round(dat["mn_resp_log_exp_dim"], digits=3))")
println(" Expert L-R: $(round(dat["mn_resp_log_exp_lr"], digits=3))")
end
end
# Report results
if !isempty(errors)
println("\n❌ VALIDATION FAILED - $(length(errors)) ERROR(S):")
for (i, err) in enumerate(errors)
println(" $i. $err")
end
return false
end
if !isempty(warnings)
println("\n⚠️ $(length(warnings)) WARNING(S):")
for (i, warn) in enumerate(warnings)
println(" $i. $warn")
end
end
if verbose
println("\n✓ DATA VALIDATION PASSED")
println("="^70)
end
return true
end
"""
Validate initialization values before passing to Stan.
Checks for common issues that cause immediate Stan crashes.
"""
function validate_init_values(init_dict::Dict; verbose=true)
verbose && println("\n" * "="^70)
verbose && println("VALIDATING INITIALIZATION VALUES")
verbose && println("="^70)
errors = String[]
warnings = String[]
for (key, value) in init_dict
# Check for NaN/Inf
if isa(value, AbstractArray) && eltype(value) <: Number
if any(isnan, value)
push!(errors, "Init '$key' contains NaN")
end
if any(isinf, value)
push!(errors, "Init '$key' contains Inf")
end
if verbose && length(value) > 0
val_array = vec(value)
println(" $key: range [$(round(minimum(val_array), digits=3)), $(round(maximum(val_array), digits=3))]")
end
elseif isa(value, Number)
if isnan(value)
push!(errors, "Init '$key' is NaN")
end
if isinf(value)
push!(errors, "Init '$key' is Inf")
end
if verbose
println(" $key: $(round(value, digits=3))")
end
end
# Check positive constraints (common Stan constraints)
# Exception: *_raw parameters are non-centered and can be any real
if (contains(string(key), "sigma") || contains(string(key), "tau") || contains(string(key), "phi")) &&
!endswith(string(key), "_raw")
if isa(value, Number) && value <= 0
push!(errors, "Init '$key' = $value violates constraint > 0")
elseif isa(value, AbstractArray) && any(value .<= 0)
push!(errors, "Init '$key' has values ≤ 0 (violates constraint > 0)")
end
end
# Check Cholesky factors are valid
if contains(string(key), "L_Omega")
if isa(value, AbstractMatrix)
# Check it's lower triangular with positive diagonal
n = size(value, 1)
if size(value, 2) != n
push!(errors, "Init '$key' is not square")
end
for i in 1:n
if value[i, i] <= 0
push!(errors, "Init '$key' has non-positive diagonal at position $i")
end
for j in (i+1):n
if abs(value[i, j]) > 1e-10
push!(warnings, "Init '$key' is not lower triangular")
break
end
end
end
end
end
# Check slope parameters for positive constraint (V2/V3 feature)
if key == "Gamma_man_slope_raw"
if isa(value, AbstractArray) && any(value .< 0)
push!(errors, "Init 'Gamma_man_slope_raw' has negative values (must be ≥ 0)")
end
end
end
# Report results
if !isempty(errors)
println("\n❌ INIT VALIDATION FAILED - $(length(errors)) ERROR(S):")
for (i, err) in enumerate(errors)
println(" $i. $err")
end
return false
end
if !isempty(warnings)
println("\n⚠️ $(length(warnings)) WARNING(S):")
for (i, warn) in enumerate(warnings)
println(" $i. $warn")
end
end
if verbose
println("\n✓ INIT VALIDATION PASSED")
println("="^70)
end
return true
end
"""
Estimate memory requirements for model
"""
function estimate_memory_requirements(dat::Dict; verbose=true, num_chains::Int=4, num_samples::Int=1000, num_threads_per_chain::Int=1)
if !verbose
return
end
println("\n" * "="^70)
println("MEMORY ESTIMATE")
println("="^70)
R = get(dat, "R", 0)
J = get(dat, "J", 0)
K_man = get(dat, "K_man", 0)
K_exp_dim = get(dat, "K_exp_dim", 0)
K_exp_lr = get(dat, "K_exp_lr", 0)
N_man = get(dat, "N_man", 0)
N_ciy = get(dat, "N_ciy", 0)
T_year = get(dat, "T_year", 0)
# Rough parameter count
theta_params = 4 * R
item_params = 4 * K_man * 2 + K_exp_dim * 3 + K_exp_lr * 2
strategic_params = get(dat, "P", 0) * get(dat, "K_man", 0) # Country-item intercepts
other_params = 4 * J + T_year + J + N_ciy + 50
total_params = theta_params + item_params + strategic_params + other_params
# Memory estimate (very rough)
# Each parameter: ~8 bytes (float64) × samples × chains
total_draws_per_param = num_samples * num_chains
bytes_per_param = 8 * total_draws_per_param
total_mb = (total_params * bytes_per_param) / (1024 * 1024)
println(" Configuration:")
println(" Chains: $num_chains")
println(" Samples per chain: $num_samples")
println(" Threads per chain: $num_threads_per_chain")
println(" Total parallel workers: $(num_chains * num_threads_per_chain)")
println(" Total parameters: ~$(total_params)")
println(" Estimated memory (samples only): ~$(round(total_mb, digits=0)) MB")
thread_scaling = max(1, num_threads_per_chain)
println(" With thread overhead (×$(thread_scaling)): ~$(round(total_mb * thread_scaling, digits=0)) MB")
println(" With safety margin (×3): ~$(round(3 * total_mb * thread_scaling, digits=0)) MB")
if total_mb * thread_scaling * 3 > 8000
println("\n⚠️ WARNING: Model may require > 8GB RAM")
end
println("="^70)
end
+277
View File
@@ -0,0 +1,277 @@
#!/usr/bin/env julia
#############################################################################
## 02_data_loading.jl
## Load and preprocess data for latent trait model
## Loads three datasets: text_data (manifesto + PolDem), expert dimension-specific, expert general L-R
##
## Supports both:
## - 4D model (V10): type_high/type_low columns for bipolar bridges
## - 2D model (V1): dim_idx + direction columns for direct estimation
#############################################################################
using DataFrames, CSV, CategoricalArrays, Statistics
#############################################################################
## UNION MAPPING: Individual party estimates via mean-constituent model
## Loads data/union_mapping.csv and builds lookup structures
#############################################################################
"""
load_union_mapping(project_root::String)
Load union_mapping.csv and build lookup dictionaries.
Returns (union_to_constituents, constituent_to_union) dicts.
If file is missing or empty, returns empty dicts (backwards compatible).
"""
function load_union_mapping(project_root::String=".")
mapping_file = joinpath(project_root, "data", "union_mapping.csv")
union_to_constituents = Dict{Int, Vector{Int}}()
constituent_to_union = Dict{Int, Int}()
if !isfile(mapping_file)
println(" No union_mapping.csv found - running without union decomposition")
return union_to_constituents, constituent_to_union
end
df = CSV.read(mapping_file, DataFrame)
if nrow(df) == 0
println(" union_mapping.csv is empty - running without union decomposition")
return union_to_constituents, constituent_to_union
end
for row in eachrow(df)
union_id = row.manifesto_pf_id
expert_id = row.expert_pf_id
if !haskey(union_to_constituents, union_id)
union_to_constituents[union_id] = Int[]
end
if !(expert_id in union_to_constituents[union_id])
push!(union_to_constituents[union_id], expert_id)
end
constituent_to_union[expert_id] = union_id
end
println(" Union mapping loaded: $(length(union_to_constituents)) unions, $(length(constituent_to_union)) constituents")
return union_to_constituents, constituent_to_union
end
#############################################################################
## SEGMENT-BASED INDEXING CONFIGURATION
## Split parties at gaps > MAX_GAP years to avoid flat posteriors
#############################################################################
const MAX_GAP = 7 # Maximum years between observations within a segment
const MIN_OBS = 2 # Minimum observations per segment (drop segments with fewer)
#############################################################################
## 2D MODEL MAPPING CONFIGURATION
## Maps type_high/type_low pairs to dim_idx + direction
#############################################################################
const TYPE_TO_DIM_DIRECTION = Dict(
# Economic dimension: pro_market = right (+1), pro_welfare = left (-1)
("pro_market", "pro_welfare") => (dim_idx=1, direction=1), # Right
("pro_welfare", "pro_market") => (dim_idx=1, direction=-1), # Left
# Cultural dimension: traditional = TAN (+1), cosmopolitan = GAL (-1)
("traditional", "cosmopolitan") => (dim_idx=2, direction=1), # TAN
("cosmopolitan", "traditional") => (dim_idx=2, direction=-1) # GAL
)
# Expert dimension mapping (lrecon -> economic, galtan/cultural -> galtan)
const EXPERT_VAR_TO_DIM = Dict(
"lrecon_ches" => 1,
"lrecon_vparty" => 1,
"welf_vparty" => 1,
"lrecon_gps" => 1,
"lrecon_poppa" => 1,
"galtan_ches" => 2,
"libcon_gps" => 2,
"immig_vparty" => 2,
"lgbt_vparty" => 2,
"culsup_vparty" => 2,
"relig_vparty" => 2,
"gender_vparty" => 2
)
function load_and_preprocess_4dim_data(start_year=1950; data_dir::String=".")
println("Loading 4D latent trait data files...")
println("Start year filter: $start_year")
data_dir != "." && println("Data directory: $data_dir")
# Load union mapping (check data_dir first, fall back to project root)
println("\nLoading union mapping...")
union_mapping_dir = isfile(joinpath(data_dir, "data", "union_mapping.csv")) ? data_dir : "."
union_to_constituents, constituent_to_union = load_union_mapping(union_mapping_dir)
# Load the three datasets
text_data_raw = CSV.read(joinpath(data_dir, "text_data.csv"), DataFrame)
expert_raw = CSV.read(joinpath(data_dir, "expert.csv"), DataFrame)
lr_data_raw = CSV.read(joinpath(data_dir, "lr_data.csv"), DataFrame)
# Filter to start year BEFORE calculating year0
text_data_raw = text_data_raw[text_data_raw.year .>= start_year, :]
expert_raw = expert_raw[expert_raw.year .>= start_year, :]
lr_data_raw = lr_data_raw[lr_data_raw.year .>= start_year, :]
println("Data filtered to $start_year onwards:")
println(" Text data (manifesto + PolDem): $(nrow(text_data_raw)) observations")
println(" Expert: $(nrow(expert_raw)) observations")
println(" L-R data: $(nrow(lr_data_raw)) observations")
# Define base year for relative time indexing
year0 = Int(minimum([minimum(text_data_raw.year), minimum(expert_raw.year), minimum(lr_data_raw.year)])) - 1
println("Base year set to: $year0")
# Create type mapping for the four dimensions
type_map = Dict(
"pro_market" => 1,
"pro_welfare" => 2,
"cosmopolitan" => 3,
"traditional" => 4
)
println("Type mapping: pro_market=1, pro_welfare=2, cosmopolitan=3, traditional=4")
# Process text data (manifesto + PolDem media)
text_data = copy(text_data_raw)
text_data = text_data[text_data.year .> year0, :]
# Add type indices for text items (V4/V10: bipolar bridge structure)
if !("type_high" in names(text_data)) || !("type_low" in names(text_data))
error("Text data must contain 'type_high' and 'type_low' columns with values: pro_market, pro_welfare, cosmopolitan, traditional")
end
text_data.type_high_idx = [type_map[t] for t in text_data.type_high]
text_data.type_low_idx = [type_map[t] for t in text_data.type_low]
# V1 (2D model): Add dim_idx and direction columns
# Maps type_high/type_low to single dimension + direction
dim_idx_man = Int[]
direction_man = Int[]
for row in eachrow(text_data)
key = (row.type_high, row.type_low)
if haskey(TYPE_TO_DIM_DIRECTION, key)
mapping = TYPE_TO_DIM_DIRECTION[key]
push!(dim_idx_man, mapping.dim_idx)
push!(direction_man, mapping.direction)
else
# Unknown mapping - this should not happen with valid data
error("Unknown type_high/type_low pair: $(row.type_high) / $(row.type_low)")
end
end
text_data.dim_idx_man = dim_idx_man
text_data.direction_man = direction_man
# Standard processing
text_data.country = categorical(text_data.country)
text_data.party = categorical(text_data.party)
text_data.var = categorical(text_data.var)
text_data.Year = Int.(text_data.year) .- year0
sort!(text_data, [:country, :party, :year, :var])
println("Text data processed: $(nrow(text_data)) observations with bipolar bridge structure")
# Process expert dimension-specific data (bipolar items like lrecon_ches, galtan_ches)
expert_dim_vars = ["lrecon_ches", "galtan_ches", "lrecon_vparty", "welf_vparty",
"lrecon_gps", "libcon_gps", "lrecon_poppa",
"immig_vparty", "lgbt_vparty", "culsup_vparty", "relig_vparty", "gender_vparty"]
expert_dim = expert_raw[in.(expert_raw.var, Ref(expert_dim_vars)), :]
expert_dim = expert_dim[(expert_dim.year .> year0) .& (expert_dim.val .>= 0) .& (expert_dim.val .<= 1), :]
# V5: Load integer observations, scale sizes, and expert counts for beta-binomial likelihood
expert_dim.val_int = Int.(expert_dim.val_int)
expert_dim.n_scale = Int.(expert_dim.n_scale)
expert_dim.n_experts = Int.(expert_dim.n_experts)
# Add type mappings for dimension-specific expert data
if !("type_low" in names(expert_dim)) || !("type_high" in names(expert_dim))
error("Expert data must contain 'type_low' and 'type_high' columns")
end
expert_dim.type_high_idx = [type_map[t] for t in expert_dim.type_high]
expert_dim.type_low_idx = [type_map[t] for t in expert_dim.type_low]
# V1 (2D model): Add dim_idx for expert dimension data
dim_idx_exp = Int[]
for row in eachrow(expert_dim)
var_name = string(row.var)
if haskey(EXPERT_VAR_TO_DIM, var_name)
push!(dim_idx_exp, EXPERT_VAR_TO_DIM[var_name])
else
# Fallback: infer from type_high/type_low
key = (row.type_high, row.type_low)
if haskey(TYPE_TO_DIM_DIRECTION, key)
push!(dim_idx_exp, TYPE_TO_DIM_DIRECTION[key].dim_idx)
else
error("Unknown expert variable: $var_name with type pair $(row.type_high) / $(row.type_low)")
end
end
end
expert_dim.dim_idx_exp = dim_idx_exp
expert_dim.country = categorical(expert_dim.country)
expert_dim.party = categorical(expert_dim.party)
expert_dim.var = categorical(expert_dim.var)
expert_dim.Year = Int.(expert_dim.year) .- year0
sort!(expert_dim, [:country, :party, :year, :var])
println("Expert dimension-specific data processed: $(nrow(expert_dim)) observations")
# Process expert general L-R data (cross-dimensional anchoring)
lr_vars = ["lr_ches", "lr_poppa", "lr_morgan"] # General left-right items
expert_lr = lr_data_raw[in.(lr_data_raw.var, Ref(lr_vars)), :]
expert_lr = expert_lr[(expert_lr.year .> year0) .& (expert_lr.val .>= 0) .& (expert_lr.val .<= 1), :]
# V5: Load integer observations, scale sizes, and expert counts for beta-binomial likelihood
expert_lr.val_int = Int.(expert_lr.val_int)
expert_lr.n_scale = Int.(expert_lr.n_scale)
expert_lr.n_experts = Int.(expert_lr.n_experts)
expert_lr.country = categorical(expert_lr.country)
expert_lr.party = categorical(expert_lr.party)
expert_lr.var = categorical(expert_lr.var)
expert_lr.Year = Int.(expert_lr.year) .- year0
sort!(expert_lr, [:country, :party, :year, :var])
println("Expert general L-R data processed: $(nrow(expert_lr)) observations")
# Validate data integrity
println("\nData validation:")
# Check text data dimension pair distribution (V4: bipolar bridges)
type_pair_counts = combine(groupby(text_data, [:type_high, :type_low]), nrow => :count)
for row in eachrow(type_pair_counts)
println(" $(row.type_high)$(row.type_low): $(row.count) text data observations")
end
# Check expert dimension-specific type pairs
type_pair_counts = combine(groupby(expert_dim, [:type_high, :type_low]), nrow => :count)
for row in eachrow(type_pair_counts)
println(" $(row.type_high) - $(row.type_low): $(row.count) expert dimension-specific observations")
end
# Check general L-R items
lr_var_counts = combine(groupby(expert_lr, :var), nrow => :count)
for row in eachrow(lr_var_counts)
println(" $(row.var): $(row.count) general L-R observations")
end
# Check overlapping parties across datasets
text_data_parties = Set(text_data.party)
expert_dim_parties = Set(expert_dim.party)
expert_lr_parties = Set(expert_lr.party)
all_parties = union(text_data_parties, expert_dim_parties, expert_lr_parties)
println("\nParty coverage:")
println(" Total unique parties: $(length(all_parties))")
println(" In text data: $(length(text_data_parties))")
println(" In expert dimension-specific: $(length(expert_dim_parties))")
println(" In expert general L-R: $(length(expert_lr_parties))")
println(" In all three datasets: $(length(intersect(text_data_parties, expert_dim_parties, expert_lr_parties)))")
return text_data, expert_dim, expert_lr, year0, union_to_constituents, constituent_to_union
end
# Execute if run directly
if abspath(PROGRAM_FILE) == @__FILE__
text_data, expert_dim, expert_lr, year0, u2c, c2u = load_and_preprocess_4dim_data()
println("4D data loading test completed successfully")
end
+947
View File
@@ -0,0 +1,947 @@
#!/usr/bin/env julia
#############################################################################
## 03_data_preparation_4dim.jl
## V10: Segment-based indexing to fix long gap interpolation issues
##
## Key change: Split parties at gaps > MAX_GAP years into independent segments.
## Each segment has its own random walk (restarts at segment start).
## Segments with < MIN_OBS observations are dropped.
#############################################################################
using DataFrames, CSV, CategoricalArrays, Statistics, StatsFuns
# Import configuration from data loading module
include("02_data_loading.jl")
"""
split_party_years_into_segments(years::Vector{Int}, max_gap::Int)
Split a party's observation years into segments based on gaps.
Returns a vector of vectors, where each inner vector contains consecutive years
with max `max_gap` years between observations.
"""
function split_party_years_into_segments(years::Vector{Int}, max_gap::Int)
if isempty(years)
return Vector{Vector{Int}}()
end
sorted_years = sort(unique(years))
segments = [Int[sorted_years[1]]]
for y in sorted_years[2:end]
if y - segments[end][end] <= max_gap
push!(segments[end], y)
else
# Gap too large - start new segment
push!(segments, [y])
end
end
return segments
end
function prepare_4dim_stan_data(manifesto, expert_dim, expert_lr, year0;
union_to_constituents=Dict{Int,Vector{Int}}(),
constituent_to_union=Dict{Int,Int}())
println("Preparing data for Stan model (Segment-based indexing)...")
println(" MAX_GAP = $MAX_GAP years, MIN_OBS = $MIN_OBS observations")
has_unions = !isempty(union_to_constituents)
if has_unions
println(" Union mapping: $(length(union_to_constituents)) unions, $(length(constituent_to_union)) constituents")
else
println(" No union mapping - standard party indexing")
end
# =========================================================================
# STEP 1: Collect observation years per party (union-aware)
# For union parties: create segments for each CONSTITUENT, not the union.
# Union manifesto years are assigned to all constituents.
# =========================================================================
# Identify which party IDs in data are unions vs standalone
# NOTE: levels() returns raw types (Int64 for integer party IDs).
# We consistently use String keys for all party lookups to avoid type mismatches.
manifesto_party_strs = Set(string.(levels(manifesto.party)))
union_ids_in_data = Set{String}()
if has_unions
for uid in keys(union_to_constituents)
uid_str = string(uid)
if uid_str in manifesto_party_strs
push!(union_ids_in_data, uid_str)
end
end
println(" Union party IDs found in manifesto data: $(length(union_ids_in_data))")
end
# Collect all party IDs that need segments
# For unions: constituents get segments; union itself does NOT
# For standalone: party gets segment as before
party_obs_years = Dict{String, Set{Int}}()
# First pass: collect non-union parties from all data sources (as strings)
all_data_parties = Set{String}()
for p in levels(manifesto.party)
push!(all_data_parties, string(p))
end
for p in levels(expert_dim.party)
push!(all_data_parties, string(p))
end
for p in levels(expert_lr.party)
push!(all_data_parties, string(p))
end
# Initialize observation years for standalone parties and constituents
for p in all_data_parties
p_int = tryparse(Int, string(p))
if p_int !== nothing && has_unions && haskey(union_to_constituents, p_int) && string(p) in union_ids_in_data
# This is a union ID in manifesto - skip it, create entries for constituents instead
continue
end
party_obs_years[p] = Set{Int}()
end
# For unions: ensure all constituents have entries
if has_unions
for (uid, constituents) in union_to_constituents
uid_str = string(uid)
if uid_str in union_ids_in_data
for cid in constituents
cid_str = string(cid)
if !haskey(party_obs_years, cid_str)
party_obs_years[cid_str] = Set{Int}()
end
end
end
end
end
# Add years from manifesto
for row in eachrow(manifesto)
p_str = string(row.party)
p_int = tryparse(Int, p_str)
if p_int !== nothing && has_unions && haskey(union_to_constituents, p_int) && p_str in union_ids_in_data
# Union manifesto: add year to ALL constituents
for cid in union_to_constituents[p_int]
cid_str = string(cid)
if haskey(party_obs_years, cid_str)
push!(party_obs_years[cid_str], row.Year)
end
end
else
if haskey(party_obs_years, p_str)
push!(party_obs_years[p_str], row.Year)
end
end
end
# Add years from expert_dim (individual party data - direct)
for row in eachrow(expert_dim)
p_str = string(row.party)
if haskey(party_obs_years, p_str)
push!(party_obs_years[p_str], row.Year)
end
end
# Add years from expert_lr (individual party data - direct)
for row in eachrow(expert_lr)
p_str = string(row.party)
if haskey(party_obs_years, p_str)
push!(party_obs_years[p_str], row.Year)
end
end
J_original = length(party_obs_years)
println("Total number of parties to create segments for: $J_original")
# =========================================================================
# STEP 2: Split parties into segments and filter by MIN_OBS
# =========================================================================
println("\nCreating segments (splitting at gaps > $MAX_GAP years)...")
segment_data = []
segment_id = 0
parties_split = 0
segments_dropped = 0
observations_dropped = 0
for (p, years_set) in party_obs_years
years = collect(years_set)
if isempty(years)
continue
end
segments = split_party_years_into_segments(years, MAX_GAP)
if length(segments) > 1
parties_split += 1
end
for (seg_num, seg_years) in enumerate(segments)
n_obs = length(seg_years)
if n_obs >= MIN_OBS
segment_id += 1
push!(segment_data, (
segment_id = segment_id,
party_id = p,
segment_num = seg_num,
year_start = minimum(seg_years),
year_end = maximum(seg_years),
n_obs = n_obs
))
else
segments_dropped += 1
observations_dropped += n_obs
end
end
end
segment_info = DataFrame(segment_data)
S = nrow(segment_info) # Number of valid segments
println(" Segments created: $S (from $J_original parties)")
println(" Parties split into multiple segments: $parties_split")
println(" Segments dropped (< $MIN_OBS obs): $segments_dropped")
println(" Observations dropped: $observations_dropped")
# Get unique parties that have at least one valid segment
all_parties = unique(segment_info.party_id)
J = length(all_parties)
println(" Parties with valid segments: $J")
# Create party-to-index mapping for the valid parties
party_to_index = Dict(all_parties .=> 1:J)
# =========================================================================
# STEP 3: Create segment-year index space (R) - consecutive within segment
# =========================================================================
println("\nCreating segment-year index space...")
segment_year_data = []
for row in eachrow(segment_info)
for y in row.year_start:row.year_end
push!(segment_year_data, (
segment_id = row.segment_id,
party_id = row.party_id,
Year = y
))
end
end
segment_year = DataFrame(segment_year_data)
segment_year.rr = 1:nrow(segment_year)
R = nrow(segment_year)
println("Total segment-year positions (R): $R")
# Compute len_theta_ts for segments (years per segment)
len_theta_ts = [row.year_end - row.year_start + 1 for row in eachrow(segment_info)]
@assert sum(len_theta_ts) == R "sum(len_theta_ts)=$(sum(len_theta_ts)) must equal R=$R"
# =========================================================================
# STEP 4: Map observations to segment-year indices (union-aware)
# =========================================================================
println("\nMapping observations to segment-year indices...")
# Create lookup: (party_str, year) -> segment_id (for valid segments only)
party_year_to_segment = Dict{Tuple{String, Int}, Int}()
for row in eachrow(segment_info)
for y in row.year_start:row.year_end
party_year_to_segment[(string(row.party_id), y)] = row.segment_id
end
end
# --- MANIFESTO: union-aware mapping ---
# For union manifesto obs: map to first constituent's segment (for ss_man).
# The actual theta averaging is handled via constituent arrays.
manifesto_segment_ids = Union{Int, Missing}[]
for row in eachrow(manifesto)
p_str = string(row.party)
p_int = tryparse(Int, p_str)
key = (p_str, row.Year)
if haskey(party_year_to_segment, key)
# Direct mapping (non-union or constituent with own segment)
push!(manifesto_segment_ids, party_year_to_segment[key])
elseif p_int !== nothing && has_unions && haskey(union_to_constituents, p_int)
# Union party: use first constituent's segment
found = false
for cid in union_to_constituents[p_int]
ckey = (string(cid), row.Year)
if haskey(party_year_to_segment, ckey)
push!(manifesto_segment_ids, party_year_to_segment[ckey])
found = true
break
end
end
if !found
push!(manifesto_segment_ids, missing)
end
else
push!(manifesto_segment_ids, missing)
end
end
manifesto.segment_id = manifesto_segment_ids
n_manifesto_before = nrow(manifesto)
manifesto = manifesto[.!ismissing.(manifesto.segment_id), :]
manifesto.segment_id = Int.(manifesto.segment_id)
println(" Manifesto: $(nrow(manifesto))/$n_manifesto_before observations (dropped $(n_manifesto_before - nrow(manifesto)) in invalid segments)")
# --- EXPERT DIM: direct mapping (individual party data) ---
expert_dim_segment_ids = Union{Int, Missing}[]
for row in eachrow(expert_dim)
key = (string(row.party), row.Year)
if haskey(party_year_to_segment, key)
push!(expert_dim_segment_ids, party_year_to_segment[key])
else
push!(expert_dim_segment_ids, missing)
end
end
expert_dim.segment_id = expert_dim_segment_ids
n_expert_dim_before = nrow(expert_dim)
expert_dim = expert_dim[.!ismissing.(expert_dim.segment_id), :]
expert_dim.segment_id = Int.(expert_dim.segment_id)
println(" Expert dim: $(nrow(expert_dim))/$n_expert_dim_before observations (dropped $(n_expert_dim_before - nrow(expert_dim)) in invalid segments)")
# --- EXPERT LR: direct mapping (individual party data) ---
expert_lr_segment_ids = Union{Int, Missing}[]
for row in eachrow(expert_lr)
key = (string(row.party), row.Year)
if haskey(party_year_to_segment, key)
push!(expert_lr_segment_ids, party_year_to_segment[key])
else
push!(expert_lr_segment_ids, missing)
end
end
expert_lr.segment_id = expert_lr_segment_ids
n_expert_lr_before = nrow(expert_lr)
expert_lr = expert_lr[.!ismissing.(expert_lr.segment_id), :]
expert_lr.segment_id = Int.(expert_lr.segment_id)
println(" Expert LR: $(nrow(expert_lr))/$n_expert_lr_before observations (dropped $(n_expert_lr_before - nrow(expert_lr)) in invalid segments)")
# =========================================================================
# STEP 5: Create segment indices (ss) for each observation
# ss indexes into 1:S (segment space), used for party-level parameters
# =========================================================================
# Create segment_id to ss mapping
segment_to_ss = Dict(row.segment_id => i for (i, row) in enumerate(eachrow(segment_info)))
manifesto.ss_man = [segment_to_ss[sid] for sid in manifesto.segment_id]
expert_dim.ss_exp_dim = [segment_to_ss[sid] for sid in expert_dim.segment_id]
expert_lr.ss_exp_lr = [segment_to_ss[sid] for sid in expert_lr.segment_id]
# Validate segment indices
@assert all(1 .<= manifesto.ss_man .<= S)
@assert all(1 .<= expert_dim.ss_exp_dim .<= S)
@assert all(1 .<= expert_lr.ss_exp_lr .<= S)
# =========================================================================
# STEP 6: Map rr indices (segment-year) to datasets via leftjoin
# =========================================================================
# Create (segment_id, Year) -> rr lookup
seg_year_to_rr = Dict{Tuple{Int, Int}, Int}()
for row in eachrow(segment_year)
seg_year_to_rr[(row.segment_id, row.Year)] = row.rr
end
# Join manifesto with segment_year to get rr indices
manifesto = leftjoin(manifesto, segment_year, on=[:segment_id, :Year])
rename!(manifesto, :rr => :rr_man)
expert_dim = leftjoin(expert_dim, segment_year, on=[:segment_id, :Year])
rename!(expert_dim, :rr => :rr_exp_dim)
expert_lr = leftjoin(expert_lr, segment_year, on=[:segment_id, :Year])
rename!(expert_lr, :rr => :rr_exp_lr)
# Validate rr mappings
@assert all(!ismissing, manifesto.rr_man) "Some manifesto observations have no rr_man mapping"
@assert all(!ismissing, expert_dim.rr_exp_dim) "Some expert_dim observations have no rr_exp_dim mapping"
@assert all(!ismissing, expert_lr.rr_exp_lr) "Some expert_lr observations have no rr_exp_lr mapping"
# Convert to Int
manifesto.rr_man = Int.(manifesto.rr_man)
expert_dim.rr_exp_dim = Int.(expert_dim.rr_exp_dim)
expert_lr.rr_exp_lr = Int.(expert_lr.rr_exp_lr)
# Validate bounds
@assert all(1 .<= manifesto.rr_man .<= R) "rr_man out of bounds"
@assert all(1 .<= expert_dim.rr_exp_dim .<= R) "rr_exp_dim out of bounds"
@assert all(1 .<= expert_lr.rr_exp_lr .<= R) "rr_exp_lr out of bounds"
# Print diagnostics
n_observed = length(unique(vcat(manifesto.rr_man, expert_dim.rr_exp_dim, expert_lr.rr_exp_lr)))
println("\nSegment-years with data: $n_observed / $R ($(round(100*n_observed/R, digits=1))%)")
println("Segment-years for interpolation: $(R - n_observed)")
# =========================================================================
# STEP 6b: Build constituent arrays for union manifesto/expert observations
# For each manifesto obs: store list of constituent rr indices for averaging
# Non-union obs: single rr (n_const=1)
# Union obs: multiple rr values (n_const=len(constituents))
# =========================================================================
println("\nBuilding constituent arrays for mean-constituent model...")
# --- Manifesto constituent arrays ---
n_const_man_vec = Int[] # n_const for each manifesto obs
const_rr_man_vec = Int[] # flat array of constituent rr values
const_offset_man_vec = Int[] # offset into const_rr for each obs
offset = 1
for row in eachrow(manifesto)
p_str = string(row.party)
p_int = tryparse(Int, p_str)
if p_int !== nothing && has_unions && haskey(union_to_constituents, p_int) && p_str in union_ids_in_data
# Union manifesto obs: find rr for each constituent in this year
constituent_rrs = Int[]
for cid in union_to_constituents[p_int]
ckey = (string(cid), row.Year)
if haskey(party_year_to_segment, ckey)
sid = party_year_to_segment[ckey]
rr_key = (sid, row.Year)
if haskey(seg_year_to_rr, rr_key)
push!(constituent_rrs, seg_year_to_rr[rr_key])
end
end
end
if isempty(constituent_rrs)
# Fallback: use the rr_man already assigned
push!(constituent_rrs, row.rr_man)
end
push!(n_const_man_vec, length(constituent_rrs))
push!(const_offset_man_vec, offset)
append!(const_rr_man_vec, constituent_rrs)
offset += length(constituent_rrs)
else
# Non-union: single constituent (itself)
push!(n_const_man_vec, 1)
push!(const_offset_man_vec, offset)
push!(const_rr_man_vec, row.rr_man)
offset += 1
end
end
N_const_man_total = length(const_rr_man_vec)
n_union_man = count(x -> x > 1, n_const_man_vec)
println(" Manifesto: $(nrow(manifesto)) obs, $n_union_man union obs, $N_const_man_total total constituent entries")
# --- Expert dim constituent arrays ---
# Individual expert obs always have n_const=1 (direct mapping)
# Union-level expert obs (if any) would average — but typically expert data is at individual party level
n_const_exp_dim_vec = Int[]
const_rr_exp_dim_vec = Int[]
const_offset_exp_dim_vec = Int[]
offset = 1
for row in eachrow(expert_dim)
p_str = string(row.party)
p_int = tryparse(Int, p_str)
if p_int !== nothing && has_unions && haskey(union_to_constituents, p_int) && p_str in union_ids_in_data
# Union-level expert obs: average over constituents
constituent_rrs = Int[]
for cid in union_to_constituents[p_int]
ckey = (string(cid), row.Year)
if haskey(party_year_to_segment, ckey)
sid = party_year_to_segment[ckey]
rr_key = (sid, row.Year)
if haskey(seg_year_to_rr, rr_key)
push!(constituent_rrs, seg_year_to_rr[rr_key])
end
end
end
if isempty(constituent_rrs)
push!(constituent_rrs, row.rr_exp_dim)
end
push!(n_const_exp_dim_vec, length(constituent_rrs))
push!(const_offset_exp_dim_vec, offset)
append!(const_rr_exp_dim_vec, constituent_rrs)
offset += length(constituent_rrs)
else
# Individual party obs
push!(n_const_exp_dim_vec, 1)
push!(const_offset_exp_dim_vec, offset)
push!(const_rr_exp_dim_vec, row.rr_exp_dim)
offset += 1
end
end
N_const_exp_dim_total = length(const_rr_exp_dim_vec)
n_union_exp_dim = count(x -> x > 1, n_const_exp_dim_vec)
println(" Expert dim: $(nrow(expert_dim)) obs, $n_union_exp_dim union obs, $N_const_exp_dim_total total constituent entries")
# --- Expert LR constituent arrays ---
n_const_exp_lr_vec = Int[]
const_rr_exp_lr_vec = Int[]
const_offset_exp_lr_vec = Int[]
offset = 1
for row in eachrow(expert_lr)
p_str = string(row.party)
p_int = tryparse(Int, p_str)
if p_int !== nothing && has_unions && haskey(union_to_constituents, p_int) && p_str in union_ids_in_data
constituent_rrs = Int[]
for cid in union_to_constituents[p_int]
ckey = (string(cid), row.Year)
if haskey(party_year_to_segment, ckey)
sid = party_year_to_segment[ckey]
rr_key = (sid, row.Year)
if haskey(seg_year_to_rr, rr_key)
push!(constituent_rrs, seg_year_to_rr[rr_key])
end
end
end
if isempty(constituent_rrs)
push!(constituent_rrs, row.rr_exp_lr)
end
push!(n_const_exp_lr_vec, length(constituent_rrs))
push!(const_offset_exp_lr_vec, offset)
append!(const_rr_exp_lr_vec, constituent_rrs)
offset += length(constituent_rrs)
else
push!(n_const_exp_lr_vec, 1)
push!(const_offset_exp_lr_vec, offset)
push!(const_rr_exp_lr_vec, row.rr_exp_lr)
offset += 1
end
end
N_const_exp_lr_total = length(const_rr_exp_lr_vec)
n_union_exp_lr = count(x -> x > 1, n_const_exp_lr_vec)
println(" Expert LR: $(nrow(expert_lr)) obs, $n_union_exp_lr union obs, $N_const_exp_lr_total total constituent entries")
# =========================================================================
# STEP 7: Filter manifesto items with sufficient observations
# =========================================================================
var_man_counts_df = combine(groupby(manifesto, :var), :var => length => :n_obs)
var_man_counts_df = var_man_counts_df[var_man_counts_df.n_obs .>= 2, :]
var_man_counts = var_man_counts_df.var
var_exp_dim_counts_df = combine(groupby(expert_dim, :var), :var => length => :n_obs)
var_exp_dim_counts_df = var_exp_dim_counts_df[var_exp_dim_counts_df.n_obs .>= 2, :]
var_exp_dim_counts = var_exp_dim_counts_df.var
var_exp_lr_counts_df = combine(groupby(expert_lr, :var), :var => length => :n_obs)
var_exp_lr_counts_df = var_exp_lr_counts_df[var_exp_lr_counts_df.n_obs .>= 2, :]
var_exp_lr_counts = var_exp_lr_counts_df.var
manifesto = manifesto[in.(manifesto.var, Ref(var_man_counts)), :]
manifesto.var_man = levelcode.(categorical(manifesto.var, levels=unique(var_man_counts)))
expert_dim = expert_dim[in.(expert_dim.var, Ref(var_exp_dim_counts)), :]
expert_dim.var_exp_dim = levelcode.(categorical(expert_dim.var, levels=unique(var_exp_dim_counts)))
expert_lr = expert_lr[in.(expert_lr.var, Ref(var_exp_lr_counts)), :]
expert_lr.var_exp_lr = levelcode.(categorical(expert_lr.var, levels=unique(var_exp_lr_counts)))
# =========================================================================
# STEP 8: Country (group) indexing
# =========================================================================
all_groups = unique(vcat(levels(manifesto.country), levels(expert_dim.country), levels(expert_lr.country)))
P = length(all_groups)
println("Total number of unique countries (P): $P")
group_to_index = Dict(all_groups .=> 1:P)
manifesto.pp_man = [group_to_index[c] for c in manifesto.country]
expert_dim.pp_exp_dim = [group_to_index[c] for c in expert_dim.country]
expert_lr.pp_exp_lr = [group_to_index[c] for c in expert_lr.country]
@assert all(1 .<= manifesto.pp_man .<= P)
@assert all(1 .<= expert_dim.pp_exp_dim .<= P)
@assert all(1 .<= expert_lr.pp_exp_lr .<= P)
# =========================================================================
# STEP 9: Country-item-year combinations for zero-inflation model
# =========================================================================
manifesto.ciy_key = string.(manifesto.country, "_", manifesto.var_man, "_", manifesto.Year)
ciy_keys = unique(manifesto.ciy_key)
N_ciy = length(ciy_keys)
println("Total unique country-item-year combinations (N_ciy): $N_ciy")
ciy_key_to_index = Dict(ciy_keys .=> 1:N_ciy)
manifesto.ciy_idx = [ciy_key_to_index[k] for k in manifesto.ciy_key]
@assert all(1 .<= manifesto.ciy_idx .<= N_ciy)
# =========================================================================
# STEP 10: Segment-country mapping (each segment inherits from party)
# For constituents: inherit country from their union's manifesto data
# =========================================================================
party_country_dict = Dict{String, Int}()
# From data directly
for row in eachrow(manifesto)
p_str = string(row.party)
p_int = tryparse(Int, p_str)
c_idx = group_to_index[string(row.country)]
party_country_dict[p_str] = c_idx
# If union, also assign country to all constituents
if p_int !== nothing && has_unions && haskey(union_to_constituents, p_int)
for cid in union_to_constituents[p_int]
party_country_dict[string(cid)] = c_idx
end
end
end
for row in eachrow(expert_dim)
party_country_dict[string(row.party)] = group_to_index[string(row.country)]
end
for row in eachrow(expert_lr)
party_country_dict[string(row.party)] = group_to_index[string(row.country)]
end
# Map each segment to its party's country
segment_country_idx = Int[]
for row in eachrow(segment_info)
pid = string(row.party_id)
if haskey(party_country_dict, pid)
push!(segment_country_idx, party_country_dict[pid])
else
# Fallback: try to find via union mapping
pf_int = tryparse(Int, pid)
if pf_int !== nothing && haskey(constituent_to_union, pf_int)
uid = constituent_to_union[pf_int]
if haskey(party_country_dict, string(uid))
push!(segment_country_idx, party_country_dict[string(uid)])
else
error("Cannot find country for constituent $pid (union $uid)")
end
else
error("Cannot find country for party $pid")
end
end
end
@assert all(1 .<= segment_country_idx .<= P)
@assert length(segment_country_idx) == S
# Persist canonical country on segment mapping tables
segment_country = [all_groups[idx] for idx in segment_country_idx]
segment_info.country = segment_country
segment_country_by_id = Dict(row.segment_id => segment_country[i] for (i, row) in enumerate(eachrow(segment_info)))
segment_year.country = [segment_country_by_id[sid] for sid in segment_year.segment_id]
# =========================================================================
# STEP 11: Load party family data and map to segments
# =========================================================================
println("\nLoading party family data...")
party_families_file = joinpath("data", "party_families.csv")
if !isfile(party_families_file)
error("party_families.csv not found at $party_families_file")
end
party_families_df = CSV.read(party_families_file, DataFrame)
pf_to_family = Dict(row.partyfacts_id => row.family for row in eachrow(party_families_df))
all_family_names = unique(party_families_df.family)
family_to_idx = Dict(f => i for (i, f) in enumerate(all_family_names))
F = length(all_family_names)
println("Total number of party families (F): $F")
println("Family categories: ", join(all_family_names, ", "))
# Map each segment to its party's family
# For constituents: try own family first, fall back to union's family
segment_family_idx = Int[]
unmatched_segments = String[]
default_family_idx = haskey(family_to_idx, "other") ? family_to_idx["other"] : 1
for row in eachrow(segment_info)
pf_id = tryparse(Int, string(row.party_id))
if !isnothing(pf_id) && haskey(pf_to_family, pf_id)
family_name = pf_to_family[pf_id]
push!(segment_family_idx, family_to_idx[family_name])
elseif !isnothing(pf_id) && haskey(constituent_to_union, pf_id) && haskey(pf_to_family, constituent_to_union[pf_id])
# Fall back to union's family
family_name = pf_to_family[constituent_to_union[pf_id]]
push!(segment_family_idx, family_to_idx[family_name])
else
push!(segment_family_idx, default_family_idx)
push!(unmatched_segments, "$(row.party_id)_seg$(row.segment_num)")
end
end
if !isempty(unmatched_segments)
println(" Warning: $(length(unmatched_segments)) segments not matched to families (assigned to 'other')")
if length(unmatched_segments) <= 10
println(" Unmatched: ", join(unmatched_segments, ", "))
end
end
@assert all(1 .<= segment_family_idx .<= F)
@assert length(segment_family_idx) == S
# =========================================================================
# STEP 12: Find anchor segment
# With unions: use CDU (1375) as anchor (individual constituent)
# Without unions: use CDU/CSU (211) as anchor (union ID)
# =========================================================================
anchor_party_id = has_unions ? 1375 : 211
anchor_label = has_unions ? "CDU" : "CDU/CSU"
anchor_segments = filter(row -> tryparse(Int, string(row.party_id)) == anchor_party_id, segment_info)
if nrow(anchor_segments) > 0
# Pick segment with most observations
anchor_segment_idx = anchor_segments[argmax(anchor_segments.n_obs), :segment_id]
# Convert to ss index (1:S)
anchor_segment_ss = segment_to_ss[anchor_segment_idx]
println(" Anchor segment ($anchor_label, ID $anchor_party_id): segment $anchor_segment_ss ($(anchor_segments[argmax(anchor_segments.n_obs), :n_obs]) obs)")
else
println(" Warning: Anchor party $anchor_label (ID $anchor_party_id) not found, using segment 1")
anchor_segment_ss = 1
end
# =========================================================================
# STEP 13: Validation - check no long gaps remain within segments
# =========================================================================
println("\nValidating segment structure...")
rr_to_segment_year = Dict(row.rr => (segment_id=row.segment_id, year=row.Year) for row in eachrow(segment_year))
seg_obs_years = Dict(row.segment_id => Set{Int}() for row in eachrow(segment_info))
for row in eachrow(manifesto)
push!(seg_obs_years[row.segment_id], row.Year)
end
for row in eachrow(expert_dim)
push!(seg_obs_years[row.segment_id], row.Year)
end
for row in eachrow(expert_lr)
push!(seg_obs_years[row.segment_id], row.Year)
end
# Union manifesto rows contribute to every constituent through const_rr_man_vec,
# even though row.segment_id stores only a representative segment for indexing.
# Include these constituent rr values so validation matches the actual Stan data.
for rr in const_rr_man_vec
if haskey(rr_to_segment_year, rr)
sy = rr_to_segment_year[rr]
push!(seg_obs_years[sy.segment_id], sy.year)
end
end
max_internal_gap = 0
for (i, row) in enumerate(eachrow(segment_info))
seg_obs = collect(seg_obs_years[row.segment_id])
if length(seg_obs) > 1
gaps = diff(sort(unique(seg_obs)))
if !isempty(gaps)
max_gap_in_seg = maximum(gaps)
max_internal_gap = max(max_internal_gap, max_gap_in_seg)
if max_gap_in_seg > MAX_GAP
@warn "Segment $i (party $(row.party_id)) has internal gap of $max_gap_in_seg years!"
end
end
end
end
println(" Maximum internal gap within segments: $max_internal_gap years (limit: $MAX_GAP)")
# Validate all segments have >= MIN_OBS
for row in eachrow(segment_info)
@assert row.n_obs >= MIN_OBS "Segment $(row.segment_id) has only $(row.n_obs) obs (min: $MIN_OBS)"
end
println(" All segments have >= $MIN_OBS observations: PASS")
println("\nSegment-based indexing created successfully")
println(" S (segments): $S")
println(" R (segment-years): $R")
println(" J (parties with valid segments): $J")
return (manifesto=manifesto, expert_dim=expert_dim, expert_lr=expert_lr,
segment_year=segment_year, segment_info=segment_info,
all_parties=all_parties, all_groups=all_groups,
S=S, J=J, P=P, R=R, N_ciy=N_ciy, len_theta_ts=len_theta_ts,
segment_country_idx=segment_country_idx, group_to_index=group_to_index,
F=F, segment_family_idx=segment_family_idx, anchor_segment_idx=anchor_segment_ss,
# Constituent arrays for mean-constituent model
N_const_man_total=N_const_man_total,
n_const_man=n_const_man_vec,
const_offset_man=const_offset_man_vec,
const_rr_man=const_rr_man_vec,
N_const_exp_dim_total=N_const_exp_dim_total,
n_const_exp_dim=n_const_exp_dim_vec,
const_offset_exp_dim=const_offset_exp_dim_vec,
const_rr_exp_dim=const_rr_exp_dim_vec,
N_const_exp_lr_total=N_const_exp_lr_total,
n_const_exp_lr=n_const_exp_lr_vec,
const_offset_exp_lr=const_offset_exp_lr_vec,
const_rr_exp_lr=const_rr_exp_lr_vec,
union_to_constituents=union_to_constituents,
constituent_to_union=constituent_to_union)
end
function finalize_4dim_stan_data(manifesto, expert_dim, expert_lr, segment_year, segment_info,
all_parties, all_groups, group_to_index, year0, S, J, P, R, N_ciy,
len_theta_ts, segment_country_idx, F, segment_family_idx, anchor_segment_idx;
N_const_man_total=0, n_const_man=Int[], const_offset_man=Int[], const_rr_man=Int[],
N_const_exp_dim_total=0, n_const_exp_dim=Int[], const_offset_exp_dim=Int[], const_rr_exp_dim=Int[],
N_const_exp_lr_total=0, n_const_exp_lr=Int[], const_offset_exp_lr=Int[], const_rr_exp_lr=Int[])
println("Finalizing 4D Stan data structure (V10: segment-based)...")
# Map years for temporal indexing
years_all = sort(unique(vcat(manifesto.Year, expert_dim.Year, expert_lr.Year)))
T_year = length(years_all)
year_map = DataFrame(year_rel=years_all, year_ix=1:T_year)
# Apply year mapping to all datasets
manifesto = leftjoin(manifesto, year_map, on=[:Year => :year_rel])
rename!(manifesto, :year_ix => :year_for_man)
expert_dim = leftjoin(expert_dim, year_map, on=[:Year => :year_rel])
rename!(expert_dim, :year_ix => :year_for_exp_dim)
expert_lr = leftjoin(expert_lr, year_map, on=[:Year => :year_rel])
rename!(expert_lr, :year_ix => :year_for_exp_lr)
# Validate year assignments
@assert all(.!ismissing.(manifesto.year_for_man))
@assert all(.!ismissing.(expert_dim.year_for_exp_dim))
@assert all(.!ismissing.(expert_lr.year_for_exp_lr))
@assert all(1 .<= manifesto.year_for_man .<= T_year)
@assert all(1 .<= expert_dim.year_for_exp_dim .<= T_year)
@assert all(1 .<= expert_lr.year_for_exp_lr .<= T_year)
# Add small epsilon to prevent exact zeros and ones
epsilon = 1e-6
expert_dim.val = clamp.(expert_dim.val, epsilon, 1.0 - epsilon)
expert_lr.val = clamp.(expert_lr.val, epsilon, 1.0 - epsilon)
# Calculate prior means
man_positive_sample = manifesto.positive[manifesto.sample .> 0] ./ manifesto.sample[manifesto.sample .> 0]
mn_resp_log_man = StatsFuns.logit(mean(man_positive_sample))
mn_resp_log_exp_dim = StatsFuns.logit(mean(expert_dim.val))
mn_resp_log_exp_lr = StatsFuns.logit(mean(expert_lr.val))
println("Prior means calculated:")
println(" Manifesto: $(round(mn_resp_log_man, digits=3))")
println(" Expert dimension-specific: $(round(mn_resp_log_exp_dim, digits=3))")
println(" Expert general L-R: $(round(mn_resp_log_exp_lr, digits=3))")
# V6: Decade indexing for hierarchical L-R weights
expert_lr_actual_years = expert_lr.Year .+ year0
expert_lr_decade_raw = div.(expert_lr_actual_years, 10)
all_lr_decades = sort(unique(expert_lr_decade_raw))
lr_decade_to_index = Dict(all_lr_decades .=> 1:length(all_lr_decades))
dd_exp_lr = [lr_decade_to_index[d] for d in expert_lr_decade_raw]
D_lr = length(all_lr_decades)
println(" Decade indexing (V6): $D_lr decades, range $(minimum(all_lr_decades)*10)s-$(maximum(all_lr_decades)*10)s")
# Create Stan data dictionary - V10 uses S (segments) instead of J (parties)
dat_4dim = Dict(
# Common data - V10: S = number of segments
"S" => S, # NEW: Number of segments (was J)
"J" => J, # Keep J for reference (parties with valid segments)
"P" => P,
"R" => R,
"T_year" => T_year,
"len_theta_ts" => Int.(len_theta_ts),
# Segment-country mapping (V10: segments inherit country from party)
"segment_country" => segment_country_idx,
# Segment family data (V10: segments inherit family from party)
"F" => F,
"segment_family" => segment_family_idx,
# Anchor segment for identification (CDU/CSU segment)
"anchor_segment" => anchor_segment_idx,
# Manifesto data - use ss_man (segment index) instead of jj_man (party index)
"N_man" => nrow(manifesto),
"K_man" => length(unique(manifesto.var_man)),
"kk_man" => manifesto.var_man,
"ss_man" => manifesto.ss_man, # V10: segment index (was jj_man)
"rr_man" => manifesto.rr_man,
"pp_man" => manifesto.pp_man,
"positive" => manifesto.positive,
"sample" => manifesto.sample,
"year_for_man" => manifesto.year_for_man,
"type_high_idx_man" => manifesto.type_high_idx,
"type_low_idx_man" => manifesto.type_low_idx,
# V1 (2D model): dimension index and direction for text data
"dim_idx_man" => manifesto.dim_idx_man,
"direction_man" => manifesto.direction_man,
# Country-item-year data for zero-inflation
"N_ciy" => N_ciy,
"ciy_idx" => manifesto.ciy_idx,
# Expert dimension-specific data
"N_exp_dim" => nrow(expert_dim),
"K_exp_dim" => length(unique(expert_dim.var_exp_dim)),
"kk_exp_dim" => expert_dim.var_exp_dim,
"ss_exp_dim" => expert_dim.ss_exp_dim, # V10: segment index
"rr_exp_dim" => expert_dim.rr_exp_dim,
"pp_exp_dim" => expert_dim.pp_exp_dim,
# V5 K-scaling: use rounded sum (mean × K × n_scale) and total trials (K × n_scale)
"val_dim_int" => Int.(clamp.(round.(expert_dim.val .* expert_dim.n_scale .* expert_dim.n_experts), 0, expert_dim.n_scale .* expert_dim.n_experts)),
"n_total_exp_dim" => expert_dim.n_scale .* expert_dim.n_experts,
"n_experts_exp_dim" => expert_dim.n_experts,
"type_high_idx" => expert_dim.type_high_idx,
"type_low_idx" => expert_dim.type_low_idx,
# V1 (2D model): dimension index for expert dimension data
"dim_idx_exp" => expert_dim.dim_idx_exp,
# Expert general L-R data
"N_exp_lr" => nrow(expert_lr),
"K_exp_lr" => length(unique(expert_lr.var_exp_lr)),
"kk_exp_lr" => expert_lr.var_exp_lr,
"ss_exp_lr" => expert_lr.ss_exp_lr, # V10: segment index
"rr_exp_lr" => expert_lr.rr_exp_lr,
"pp_exp_lr" => expert_lr.pp_exp_lr,
# V5 K-scaling: use rounded sum (mean × K × n_scale) and total trials (K × n_scale)
"val_lr_int" => Int.(clamp.(round.(expert_lr.val .* expert_lr.n_scale .* expert_lr.n_experts), 0, expert_lr.n_scale .* expert_lr.n_experts)),
"n_total_exp_lr" => expert_lr.n_scale .* expert_lr.n_experts,
"n_experts_exp_lr" => expert_lr.n_experts,
# V6: Decade indexing for hierarchical L-R weights
"D_lr" => D_lr,
"dd_exp_lr" => dd_exp_lr,
# Prior information
"mn_resp_log_man" => mn_resp_log_man,
"mn_resp_log_exp_dim" => mn_resp_log_exp_dim,
"mn_resp_log_exp_lr" => mn_resp_log_exp_lr,
# Constituent arrays for mean-constituent model (V4)
"N_const_man_total" => max(1, N_const_man_total),
"n_const_man" => isempty(n_const_man) ? ones(Int, nrow(manifesto)) : n_const_man,
"const_offset_man" => isempty(const_offset_man) ? collect(1:nrow(manifesto)) : const_offset_man,
"const_rr_man" => isempty(const_rr_man) ? manifesto.rr_man : const_rr_man,
"N_const_exp_dim_total" => max(1, N_const_exp_dim_total),
"n_const_exp_dim" => isempty(n_const_exp_dim) ? ones(Int, nrow(expert_dim)) : n_const_exp_dim,
"const_offset_exp_dim" => isempty(const_offset_exp_dim) ? collect(1:nrow(expert_dim)) : const_offset_exp_dim,
"const_rr_exp_dim" => isempty(const_rr_exp_dim) ? expert_dim.rr_exp_dim : const_rr_exp_dim,
"N_const_exp_lr_total" => max(1, N_const_exp_lr_total),
"n_const_exp_lr" => isempty(n_const_exp_lr) ? ones(Int, nrow(expert_lr)) : n_const_exp_lr,
"const_offset_exp_lr" => isempty(const_offset_exp_lr) ? collect(1:nrow(expert_lr)) : const_offset_exp_lr,
"const_rr_exp_lr" => isempty(const_rr_exp_lr) ? expert_lr.rr_exp_lr : const_rr_exp_lr
)
println("4D Stan data dictionary created with $(length(dat_4dim)) elements")
# Print summary statistics
println("\nData summary (V10: Segment-based):")
println(" Segments: $(dat_4dim["S"])")
println(" Parties with valid segments: $(dat_4dim["J"])")
println(" Segment-year combinations: $(dat_4dim["R"])")
println(" Manifesto observations: $(dat_4dim["N_man"])")
println(" Expert dimension-specific observations: $(dat_4dim["N_exp_dim"])")
println(" Expert general L-R observations: $(dat_4dim["N_exp_lr"])")
println(" Unique manifesto items: $(dat_4dim["K_man"])")
println(" Unique expert dimension-specific items: $(dat_4dim["K_exp_dim"])")
println(" Unique expert general L-R items: $(dat_4dim["K_exp_lr"])")
println(" Years: $(dat_4dim["T_year"])")
return (dat_4dim=dat_4dim, manifesto=manifesto, expert_dim=expert_dim,
expert_lr=expert_lr, T_year=T_year, segment_year=segment_year,
segment_info=segment_info)
end
# Execute if run directly
if abspath(PROGRAM_FILE) == @__FILE__
println("Run from main script to execute the full 4D pipeline")
end
File diff suppressed because it is too large Load Diff
+174
View File
@@ -0,0 +1,174 @@
#!/usr/bin/env julia
#############################################################################
## 05_results_processing.jl
## Extract and process 4D model results with diagnostics
## Adapted from old_project for latent traits only (no election effects)
#############################################################################
using StanSample, DataFrames, Statistics
function extract_model_results_4dim(stanmodel)
"""
Extract model results for 4D latent trait model
Simplified version - no election effects (pure latent traits)
"""
println("Extracting 4D model results...")
try
println("Model completed successfully - extracting results")
# For 4D latent trait model, we save the full stanmodel object
# Post-estimation will extract specific parameters later
return (
samples = stanmodel, # Save the full stanmodel with all MCMC samples
extraction_status = "success"
)
catch e
println("Error in result extraction: $e")
return (
samples = "error",
extraction_status = "error",
error_message = string(e)
)
end
end
function compute_model_diagnostics(stanmodel_result)
"""
Compute convergence diagnostics from Stan model
Returns R-hat, ESS statistics, and overall convergence assessment
stanmodel_result can be either:
- A SampleModel object directly
- A named tuple from run_4dim_stan_model containing .stanmodel
"""
println("Computing model diagnostics...")
try
# Handle both direct SampleModel and named tuple from run_4dim_stan_model
stanmodel = if hasproperty(stanmodel_result, :stanmodel)
stanmodel_result.stanmodel
else
stanmodel_result
end
# Get REAL diagnostics using StanSample.jl
diagnostics_summary = read_summary(stanmodel)
# Extract real Rhat and ESS values. Stan summary column names differ
# across CmdStan/StanSample versions, so resolve aliases explicitly.
summary_names = names(diagnostics_summary)
rhat_col = if "r_hat" in summary_names
"r_hat"
elseif "R_hat" in summary_names
"R_hat"
elseif "RHat" in summary_names
"RHat"
else
error("No R-hat column found in summary. Columns: $(join(summary_names, ", "))")
end
ess_col = if "ess_bulk" in summary_names
"ess_bulk"
elseif "ess" in summary_names
"ess"
elseif "ESS_bulk" in summary_names
"ESS_bulk"
elseif "n_eff" in summary_names
"n_eff"
else
error("No ESS column found in summary. Columns: $(join(summary_names, ", "))")
end
rhat_vals = diagnostics_summary[!, rhat_col]
ess_bulk_vals = diagnostics_summary[!, ess_col]
# Compute real statistics (handle NaN values properly)
# Use isfinite to exclude both missing and NaN values
valid_rhat = filter(isfinite, rhat_vals)
valid_ess = filter(isfinite, ess_bulk_vals)
mean_rhat = length(valid_rhat) > 0 ? mean(valid_rhat) : NaN
max_rhat = length(valid_rhat) > 0 ? maximum(valid_rhat) : NaN
mean_ess = length(valid_ess) > 0 ? mean(valid_ess) : NaN
min_ess = length(valid_ess) > 0 ? minimum(valid_ess) : NaN
# Count problematic parameters (use isfinite for consistent counting)
high_rhat_count = count(x -> isfinite(x) && x > 1.1, rhat_vals)
moderate_rhat_count = count(x -> isfinite(x) && x > 1.05, rhat_vals)
low_ess_count = count(x -> isfinite(x) && x < 400, ess_bulk_vals)
very_low_ess_count = count(x -> isfinite(x) && x < 100, ess_bulk_vals)
# Total parameter count
total_params = length(valid_rhat)
# Overall assessment (handle NaN values)
if isnan(max_rhat) || total_params == 0
convergence_status = "insufficient_data"
else
excellent_convergence = max_rhat < 1.05 && high_rhat_count == 0 && very_low_ess_count == 0
good_convergence = max_rhat < 1.1 && high_rhat_count < 5 && very_low_ess_count < total_params * 0.1
acceptable_convergence = max_rhat < 1.2 && high_rhat_count < total_params * 0.1
if excellent_convergence
convergence_status = "excellent"
elseif good_convergence
convergence_status = "good"
elseif acceptable_convergence
convergence_status = "acceptable"
else
convergence_status = "poor"
end
end
println("\nDiagnostics computed:")
println(" Total parameters: $total_params")
println(" Mean R-hat: $(round(mean_rhat, digits=4))")
println(" Max R-hat: $(round(max_rhat, digits=4))")
println(" High R-hat count (>1.1): $high_rhat_count")
println(" Mean ESS: $(round(mean_ess, digits=0))")
println(" Min ESS: $(round(min_ess, digits=0))")
println(" Very low ESS count (<100): $very_low_ess_count")
println(" Convergence status: $convergence_status")
return (
diagnostics_summary = diagnostics_summary,
convergence_status = convergence_status,
mean_rhat = mean_rhat,
max_rhat = max_rhat,
mean_ess = mean_ess,
min_ess = min_ess,
high_rhat_count = high_rhat_count,
moderate_rhat_count = moderate_rhat_count,
low_ess_count = low_ess_count,
very_low_ess_count = very_low_ess_count,
total_params = total_params
)
catch e
println("Error in diagnostics computation: $e")
println("Stack trace:")
showerror(stdout, e, catch_backtrace())
return (
diagnostics_summary = "error",
convergence_status = "error",
mean_rhat = 999.0,
max_rhat = 999.0,
mean_ess = 0.0,
min_ess = 0.0,
high_rhat_count = 999,
moderate_rhat_count = 999,
low_ess_count = 999,
very_low_ess_count = 999,
total_params = 0,
error_message = string(e)
)
end
end
# Execute if run directly
if abspath(PROGRAM_FILE) == @__FILE__
println("Run from main run_model.jl to execute the full pipeline")
end
+428
View File
@@ -0,0 +1,428 @@
#!/usr/bin/env julia
#############################################################################
## 06_save_model.jl
## CSV-First Save Architecture
## Robust, portable, no serialization issues
#############################################################################
module RobustSave
using Dates, Printf, CSV, DataFrames, JSON
export robust_save_model_csv, robust_save_model
"""
CSV-First Save Architecture
When chains_already_saved=true (BULLETPROOF MODE):
- Chains already saved to run_dir/chains/ by model execution
- Just verify chains and add metadata/data files
- Even if this function crashes, chains are SAFE
When chains_already_saved=false (legacy mode):
- Copy chains from temp directory to new run directory
- Add metadata/data files
Saves model results as:
1. CSV chain files (source of truth - never fail)
2. Data CSVs (original inputs for reproducibility)
3. Simple metadata.json (no complex types)
4. Human-readable README.txt
No JLD2, no serialization issues, fully portable and reproducible.
"""
function robust_save_model_csv(
run_dir_or_temp::String,
data_dict::Dict,
original_data::Dict,
metadata::Dict;
chains_already_saved::Bool=false
)
println("\n" * "=" ^ 70)
if chains_already_saved
println("ADDING METADATA TO EXISTING RUN (chains already secured)")
else
println("CSV-FIRST MODEL SAVE")
end
println("=" ^ 70)
# Determine directories based on mode
if chains_already_saved
# Chains already saved - run_dir_or_temp IS the run directory
run_dir = run_dir_or_temp
chains_dir = joinpath(run_dir, "chains")
data_dir = joinpath(run_dir, "data")
run_id = basename(run_dir)
timestamp = replace(run_id, "run_" => "")
println("Run directory: $run_dir")
println("Mode: Chains already secured, adding metadata")
# Verify chains directory exists
if !isdir(chains_dir)
error("CRITICAL: Chains directory not found: $chains_dir")
end
# Count existing chain files
chain_files = filter(f -> endswith(f, ".csv") && contains(f, "chain"), readdir(chains_dir))
if isempty(chain_files)
error("CRITICAL: No chain CSV files found in $chains_dir")
end
println("Found $(length(chain_files)) chain files already saved")
# Calculate total size
total_size_gb = 0.0
for chain_file in chain_files
chain_path = joinpath(chains_dir, chain_file)
total_size_gb += filesize(chain_path) / (1024^3)
end
println("✓ Chains verified ($(round(total_size_gb, digits=2)) GB total)")
else
# Legacy mode - create new run directory and copy chains
temp_csv_dir = run_dir_or_temp
timestamp = Dates.format(Dates.now(), "yyyy-mm-dd_HH-MM-SS")
run_id = "run_$(timestamp)"
run_dir = joinpath("outputs", "model_outputs", "latest", run_id)
chains_dir = joinpath(run_dir, "chains")
data_dir = joinpath(run_dir, "data")
println("Run ID: $run_id")
println("Output directory: $run_dir")
# Create directory structure
mkpath(chains_dir)
# STEP 1: Copy CSV chain files
println("\n" * "=" ^ 70)
println("STEP 1: Copying MCMC chain CSV files")
println("=" ^ 70)
println("Source: $temp_csv_dir")
println("Destination: $chains_dir")
csv_files = filter(f -> endswith(f, ".csv"), readdir(temp_csv_dir))
chain_files = filter(f -> contains(f, "chain"), csv_files)
if isempty(chain_files)
error("No chain CSV files found in $temp_csv_dir")
end
println("Found $(length(chain_files)) chain files")
total_size_gb = 0.0
for (i, csv_file) in enumerate(sort(chain_files))
src_path = joinpath(temp_csv_dir, csv_file)
# Rename to standard format: chain_1.csv, chain_2.csv, etc.
dest_filename = "chain_$i.csv"
dest_path = joinpath(chains_dir, dest_filename)
src_size = filesize(src_path)
size_gb = src_size / (1024^3)
total_size_gb += size_gb
println(" Copying $csv_file$dest_filename ($(round(size_gb, digits=2)) GB)")
cp(src_path, dest_path, force=true)
# Verify copy with size check
dest_size = filesize(dest_path)
if dest_size != src_size
error("CRITICAL: Size mismatch for $dest_filename! Source: $src_size, Dest: $dest_size")
end
end
println("✓ All chains copied and verified ($(round(total_size_gb, digits=2)) GB total)")
end
# Create data directory
mkpath(data_dir)
# STEP 2: Save data CSVs
println("\n" * "=" ^ 70)
println("STEP 2: Saving original data CSVs")
println("=" ^ 70)
for (name, df) in original_data
if isa(df, DataFrame)
csv_path = joinpath(data_dir, "$(name).csv")
println(" Saving $(name).csv ($(nrow(df)) rows)")
CSV.write(csv_path, df)
end
end
println("✓ Data CSVs saved")
# STEP 3: Save Stan data dictionary as JSON
println("\n" * "=" ^ 70)
println("STEP 3: Saving Stan data dictionary")
println("=" ^ 70)
# Convert data_dict to JSON-serializable format
stan_data_json = Dict{String, Any}()
for (k, v) in data_dict
try
# Only save simple types (numbers, arrays of numbers)
if isa(v, Number) || isa(v, AbstractArray{<:Number})
stan_data_json[k] = v
elseif isa(v, AbstractArray)
# Try to convert, skip if fails
try
stan_data_json[k] = collect(v)
catch
println(" Skipping $k (complex type)")
end
end
catch e
println(" Warning: Could not serialize $k: $e")
end
end
stan_data_path = joinpath(data_dir, "stan_data.json")
open(stan_data_path, "w") do f
JSON.print(f, stan_data_json, 2)
end
println("✓ Stan data saved to stan_data.json")
# Count chain files for metadata
chain_files_final = filter(f -> endswith(f, ".csv") && contains(f, "chain"), readdir(chains_dir))
num_chains = length(chain_files_final)
# Recalculate total_size_gb if in chains_already_saved mode
if chains_already_saved
total_size_gb = 0.0
for chain_file in chain_files_final
chain_path = joinpath(chains_dir, chain_file)
total_size_gb += filesize(chain_path) / (1024^3)
end
end
# STEP 4: Save metadata
println("\n" * "=" ^ 70)
println("STEP 4: Saving metadata")
println("=" ^ 70)
# Add run info to metadata
metadata["run_id"] = run_id
metadata["timestamp"] = timestamp
metadata["files"] = Dict(
"chains" => ["chains/chain_$i.csv" for i in 1:num_chains],
"data" => readdir(data_dir),
"chain_size_gb" => num_chains > 0 ? round(total_size_gb / num_chains, digits=2) : 0.0,
"total_size_gb" => round(total_size_gb, digits=2)
)
metadata_path = joinpath(run_dir, "metadata.json")
open(metadata_path, "w") do f
JSON.print(f, metadata, 2)
end
println("✓ Metadata saved to metadata.json")
# STEP 5: Generate README
println("\n" * "=" ^ 70)
println("STEP 5: Generating README")
println("=" ^ 70)
readme_path = joinpath(run_dir, "README.txt")
generate_readme(readme_path, run_id, metadata, num_chains, total_size_gb)
println("✓ README generated")
# STEP 6: Final verification
println("\n" * "=" ^ 70)
println("STEP 6: Verification")
println("=" ^ 70)
# Verify all chain files exist and are readable
all_good = true
verified_files = Dict{String, Dict{String, Any}}()
for i in 1:num_chains
chain_path = joinpath(chains_dir, "chain_$i.csv")
if !isfile(chain_path)
println(" ✗ Missing: chain_$i.csv")
all_good = false
else
# Quick read test and size check
try
CSV.File(chain_path; limit=1)
file_size_gb = filesize(chain_path) / (1024^3)
verified_files["chain_$i.csv"] = Dict(
"path" => chain_path,
"size_gb" => file_size_gb,
"verified" => true
)
println(" ✓ chain_$i.csv verified ($(round(file_size_gb, digits=2)) GB)")
catch e
println(" ✗ Cannot read chain_$i.csv: $e")
all_good = false
end
end
end
if !all_good
error("Verification failed - some files are missing or corrupted")
end
println("\n" * "=" ^ 70)
println("✓ MODEL SAVED SUCCESSFULLY")
println("=" ^ 70)
println("Run directory: $run_dir")
println("Total size: $(round(total_size_gb, digits=2)) GB")
println("Status: All files verified and ready")
println("=" ^ 70)
# Return verification details for cleanup
return (
run_dir = run_dir,
verified_files = verified_files,
total_size_gb = total_size_gb,
verification_passed = all_good,
num_chains = num_chains
)
end
function generate_readme(
filepath::String,
run_id::String,
metadata::Dict,
num_chains::Int,
total_size_gb::Float64
)
"""Generate human-readable README file"""
open(filepath, "w") do f
write(f, "=" ^ 78 * "\n")
write(f, "4D LATENT TRAIT MODEL - MODEL RUN RESULTS\n")
write(f, "=" ^ 78 * "\n\n")
write(f, "Run ID: $run_id\n")
write(f, "Model: $(get(metadata, "model_file", "unknown"))\n")
write(f, "Date: $(Dates.format(Dates.now(), "yyyy-mm-dd HH:MM:SS"))\n")
write(f, "Status: $(get(metadata, "convergence_status", "unknown"))\n\n")
write(f, "=" ^ 78 * "\n")
write(f, "DIRECTORY CONTENTS\n")
write(f, "=" ^ 78 * "\n\n")
write(f, "chains/\n")
for i in 1:num_chains
write(f, " ├── chain_$i.csv\n")
end
write(f, " Total: $(get(metadata, "num_chains", num_chains)) chains × " *
"$(get(metadata, "num_samples", "?")) samples\n")
write(f, " Size: $(round(total_size_gb, digits=2)) GB\n\n")
write(f, "data/\n")
write(f, " ├── text_data.csv\n")
write(f, " ├── expert_dim.csv\n")
write(f, " ├── expert_lr.csv\n")
write(f, " ├── segment_year_map.csv (V10)\n")
write(f, " ├── segment_info.csv (V10)\n")
write(f, " └── stan_data.json\n\n")
write(f, "=" ^ 78 * "\n")
write(f, "MODEL CONFIGURATION\n")
write(f, "=" ^ 78 * "\n\n")
write(f, "Chains: $(get(metadata, "num_chains", "?"))\n")
write(f, "Warmup: $(get(metadata, "num_warmup", "?"))\n")
write(f, "Samples: $(get(metadata, "num_samples", "?"))\n")
write(f, "Adapt delta: $(get(metadata, "adapt_delta", "?"))\n")
write(f, "Max depth: $(get(metadata, "max_depth", "?"))\n\n")
write(f, "Dimensions: $(join(get(metadata, "dimensions", ["?"]), ", "))\n\n")
write(f, "=" ^ 78 * "\n")
write(f, "HOW TO USE THESE RESULTS\n")
write(f, "=" ^ 78 * "\n\n")
write(f, "To extract party positions:\n\n")
write(f, " julia 02_post_estimation.jl\n\n")
write(f, "This will read the CSV files and generate party_positions_[timestamp].csv\n")
write(f, "with uncertainty estimates (SE, credible intervals).\n\n")
write(f, "=" ^ 78 * "\n")
write(f, "Generated: $(Dates.format(Dates.now(), "yyyy-mm-dd HH:MM:SS"))\n")
write(f, "=" ^ 78 * "\n")
end
end
"""
Wrapper for robust_save_model_csv that handles the stanmodel tuple from run_4dim_stan_model.
The model execution now saves chains BEFORE returning, so this function:
1. Verifies chains are already saved in final_run_dir
2. Adds metadata and data files
3. Returns the output path
Arguments:
- stanmodel_tuple: Named tuple from run_4dim_stan_model (contains .stanmodel, .final_run_dir, etc.)
- model_data: Dict with data_dict, original data, and metadata
- output_dir: Base output directory (ignored - uses stanmodel_tuple.final_run_dir)
- compress: Ignored (CSV-first architecture)
- keep_local_backups: Ignored (chains already saved)
"""
function robust_save_model(
stanmodel_tuple,
model_data::Dict,
output_dir::String;
compress::Bool=true,
keep_local_backups::Int=2
)
# Extract the final run directory from the stanmodel tuple
if !hasproperty(stanmodel_tuple, :final_run_dir)
error("stanmodel_tuple missing :final_run_dir - chains may not be saved!")
end
final_run_dir = stanmodel_tuple.final_run_dir
# Prepare original data for saving
original_data = Dict{String, Any}()
if haskey(model_data, "manifesto")
original_data["text_data"] = model_data["manifesto"]
end
if haskey(model_data, "expert_dim")
original_data["expert_dim"] = model_data["expert_dim"]
end
if haskey(model_data, "expert_lr")
original_data["expert_lr"] = model_data["expert_lr"]
end
# V10: Save segment_year_map and segment_info for post-estimation
if haskey(model_data, "segment_year")
original_data["segment_year_map"] = model_data["segment_year"]
end
if haskey(model_data, "segment_info")
original_data["segment_info"] = model_data["segment_info"]
end
# V9 fallback: Save party_year_map for post-estimation (includes interpolated years)
if haskey(model_data, "party_year")
original_data["party_year_map"] = model_data["party_year"]
end
# Prepare metadata
metadata = Dict{String, Any}()
if haskey(model_data, "model_info")
for (k, v) in model_data["model_info"]
metadata[string(k)] = v
end
end
# Get data_dict
data_dict = get(model_data, "data_dict", Dict{String, Any}())
# Call the CSV-first save with chains_already_saved=true
result = robust_save_model_csv(
final_run_dir,
data_dict,
original_data,
metadata;
chains_already_saved=true
)
return result.run_dir
end
end # module
@@ -0,0 +1,150 @@
#!/usr/bin/env julia
#############################################################################
## performance_monitoring.jl
## Post-run performance diagnostics for Stan sampling jobs
#############################################################################
using StanSample
using Statistics: mean, median
using Dates
function _safe_column(df, candidates::Vector{String})
for candidate in candidates
sym = Symbol(candidate)
if sym in names(df)
return df[!, sym]
elseif candidate in names(df)
return df[!, candidate]
end
end
return nothing
end
function _clean_values(vec)
cleaned = Float64[]
for v in vec
if v isa Missing || v === nothing
continue
end
try
value = Float64(v)
isfinite(value) && push!(cleaned, value)
catch
continue
end
end
return cleaned
end
function _summarize_vector(vec)
if vec === nothing
return Dict{String,Any}("available" => false)
end
cleaned = _clean_values(vec)
if isempty(cleaned)
return Dict{String,Any}("available" => false)
end
return Dict{String,Any}(
"available" => true,
"count" => length(cleaned),
"mean" => mean(cleaned),
"median" => median(cleaned),
"min" => minimum(cleaned),
"max" => maximum(cleaned)
)
end
function monitor_sampling_performance!(stanmodel;
run_metrics::Union{Nothing,Dict{String,Any}}=nothing,
metrics_path::Union{Nothing,String}=nothing,
csv_paths::Union{Nothing,Vector{String}}=nothing,
aggregate_metrics::Union{Nothing,Dict{String,Any}}=nothing,
max_depth::Union{Nothing,Int}=nothing)
csv_paths === nothing && (csv_paths = discover_stan_csvs([stanmodel.tmpdir]))
aggregate_metrics === nothing && begin
_, aggregate_metrics = collect_run_metrics(csv_paths; max_depth=max_depth)
end
summary_df = nothing
try
summary_df = read_summary(stanmodel)
catch e
println("Warning: could not read Stan summary: $e")
end
performance = Dict{String,Any}(
"generated_at" => Dates.format(Dates.now(), "yyyy-mm-ddTHH:MM:SS"),
"csv_paths" => csv_paths
)
if summary_df !== nothing
performance["ess_bulk"] = _summarize_vector(_safe_column(summary_df, ["ess_bulk", "ess"]))
performance["ess_tail"] = _summarize_vector(_safe_column(summary_df, ["ess_tail"]))
performance["ess_per_sec"] = _summarize_vector(_safe_column(summary_df, ["ess_per_sec", "n_eff/s"]))
performance["r_hat"] = _summarize_vector(_safe_column(summary_df, ["r_hat"]))
if haskey(performance["r_hat"], "available") && performance["r_hat"]["available"]
performance["r_hat"]["max"] = maximum(_clean_values(_safe_column(summary_df, ["r_hat"])))
end
performance["parameters_considered"] = size(summary_df, 1)
else
performance["ess_bulk"] = Dict{String,Any}("available" => false)
performance["ess_tail"] = Dict{String,Any}("available" => false)
performance["ess_per_sec"] = Dict{String,Any}("available" => false)
performance["r_hat"] = Dict{String,Any}("available" => false)
performance["parameters_considered"] = 0
end
divergences = get(aggregate_metrics, "divergences", 0)
total_samples = stanmodel.num_samples * stanmodel.num_chains
divergence_rate = total_samples > 0 ? divergences / total_samples : nothing
performance["divergences"] = Dict{String,Any}(
"count" => divergences,
"rate" => divergence_rate,
"total_draws" => total_samples
)
performance["leapfrog"] = Dict{String,Any}(
"mean" => get(aggregate_metrics, "mean_leapfrog", nothing)
)
performance["step_size"] = Dict{String,Any}(
"mean" => get(aggregate_metrics, "mean_step_size", nothing)
)
sampling_seconds = get(aggregate_metrics, "sampling_seconds", nothing)
if sampling_seconds !== nothing && sampling_seconds > 0
performance["throughput"] = Dict{String,Any}(
"samples_per_second" => (total_samples / sampling_seconds),
"seconds_sampling" => sampling_seconds
)
else
performance["throughput"] = Dict{String,Any}(
"samples_per_second" => nothing,
"seconds_sampling" => sampling_seconds
)
end
if run_metrics !== nothing
run_metrics["performance"] = performance
if metrics_path !== nothing
safe_write_json(metrics_path, run_metrics)
end
elseif metrics_path !== nothing
temp_metrics = Dict{String,Any}("performance" => performance)
safe_write_json(metrics_path, temp_metrics)
end
println("\nPERFORMANCE SUMMARY")
println(" ESS bulk (mean): $(get(performance["ess_bulk"], "mean", "n/a"))")
println(" ESS/sec (mean): $(get(performance["ess_per_sec"], "mean", "n/a"))")
println(" Divergences: $(divergences)")
println(" Divergence rate: $(divergence_rate === nothing ? "n/a" : round(divergence_rate, digits=6))")
println(" Mean leapfrog steps: $(get(performance["leapfrog"], "mean", "n/a"))")
return performance
end
+450
View File
@@ -0,0 +1,450 @@
#!/usr/bin/env julia
#############################################################################
## validate_construct.jl
## Construct validity: Party family ordering and temporal stability
##
## Following Claassen (2019), this script validates:
## 1. Party family ordering: Do family means follow theoretically expected orderings?
## 2. Temporal stability: Flag parties with implausible position changes
##
## Uses ParlGov party family classifications (Döring & Manow 2024) via PartyFacts IDs.
#############################################################################
using CSV, DataFrames, Statistics, StatsBase, Dates, Printf
# Family code → display name mapping
const FAMILY_DISPLAY_NAMES = Dict(
"com" => "Communist/Far Left",
"eco" => "Green/Ecological",
"soc" => "Social Democratic",
"lib" => "Liberal",
"chr" => "Christian Democratic",
"con" => "Conservative",
"right" => "Radical Right"
)
# Substantive families (drop Specialist, Other, Agrarian — heterogeneous or ambiguous)
const SUBSTANTIVE_FAMILIES = Set(["com", "eco", "soc", "lib", "chr", "con", "right"])
# Expected orderings (theoretically motivated)
# Economic: Communist < Social Democratic < Green < Christian Democratic < Conservative
# (5-family core — Liberal position is ambiguous cross-nationally)
const EXPECTED_ECONOMIC_ORDER = ["com", "soc", "eco", "chr", "con"]
# Cultural: Green < Liberal < Social Democratic < Christian Democratic < Conservative < Radical Right
const EXPECTED_GALTAN_ORDER = ["eco", "lib", "soc", "chr", "con", "right"]
function load_model_output(base_dir::String=".")
"""Load the most recent 2D model party positions output"""
position_files = filter(f -> startswith(f, "party_positions_") && endswith(f, ".csv") &&
!endswith(f, "_metadata.txt") && !endswith(f, "_tables.tex"), readdir(base_dir))
legacy_files = filter(f -> startswith(f, "party_positions_v1_") && endswith(f, ".csv"), readdir(base_dir))
append!(position_files, legacy_files)
if !isempty(position_files)
latest = sort(position_files)[end]
println("Loading model output: $latest")
return CSV.read(joinpath(base_dir, latest), DataFrame), latest
end
# Check output estimations directory
est_dir = joinpath(base_dir, "outputs", "estimations", "latest")
if isdir(est_dir)
est_files = filter(f -> startswith(f, "party_positions_") && endswith(f, ".csv") &&
!endswith(f, "_metadata.txt") && !endswith(f, "_tables.tex"), readdir(est_dir))
if !isempty(est_files)
latest = sort(est_files)[end]
println("Loading model output: outputs/estimations/latest/$latest")
return CSV.read(joinpath(est_dir, latest), DataFrame), latest
end
end
error("No party_positions_*.csv found. Run 02_post_estimation.jl first.")
end
function validate_party_families(model::DataFrame)
"""Check whether party family means follow theoretically expected orderings"""
println("\n" * "="^60)
println("PARTY FAMILY ORDERING VALIDATION")
println("="^60)
println("\nUsing ParlGov family classifications (Döring & Manow 2024)")
println()
party_col = hasproperty(model, :party_id) ? :party_id : :party
# Load party families
families_df = CSV.read("data/party_families.csv", DataFrame)
# Join to model output
model_with_families = innerjoin(model, families_df, on=party_col => :partyfacts_id)
# Filter to substantive families
filter!(r -> r.family in SUBSTANTIVE_FAMILIES, model_with_families)
n_parties = length(unique(model_with_families[!, party_col]))
n_obs = nrow(model_with_families)
println(" Matched $n_parties parties ($n_obs party-years) across $(length(SUBSTANTIVE_FAMILIES)) families")
println()
# Compute family means
family_stats = combine(groupby(model_with_families, :family)) do df
DataFrame(
n_parties = length(unique(df[!, party_col])),
n_obs = nrow(df),
mean_economic = mean(df.economic_lr),
sd_economic = std(df.economic_lr),
mean_galtan = mean(df.galtan),
sd_galtan = std(df.galtan)
)
end
# Add display names
family_stats.family_name = [get(FAMILY_DISPLAY_NAMES, f, f) for f in family_stats.family]
# Sort by economic mean for display
sort!(family_stats, :mean_economic)
# Print table
@printf(" %-22s %7s %7s %10s %10s %10s %10s\n",
"Family", "Parties", "Obs", "Econ Mean", "Econ SD", "Cult Mean", "Cult SD")
println(" " * "-"^76)
for row in eachrow(family_stats)
@printf(" %-22s %7d %7d %10.3f %10.3f %10.3f %10.3f\n",
row.family_name, row.n_parties, row.n_obs,
row.mean_economic, row.sd_economic, row.mean_galtan, row.sd_galtan)
end
# Compute Spearman rank correlations for expected orderings
println()
# Economic ordering
econ_lookup = Dict(row.family => row.mean_economic for row in eachrow(family_stats))
econ_observed = [econ_lookup[f] for f in EXPECTED_ECONOMIC_ORDER if haskey(econ_lookup, f)]
econ_expected_ranks = collect(1:length(econ_observed))
econ_observed_ranks = ordinalrank(econ_observed)
rho_econ = corspearman(Float64.(econ_expected_ranks), Float64.(econ_observed_ranks))
println(@sprintf(" Economic ordering (5-family core): Spearman ρ = %.3f", rho_econ))
econ_families_used = [f for f in EXPECTED_ECONOMIC_ORDER if haskey(econ_lookup, f)]
econ_names = [get(FAMILY_DISPLAY_NAMES, f, f) for f in econ_families_used]
println(" Expected: ", join(econ_names, " < "))
observed_econ_order = econ_families_used[sortperm(econ_observed)]
observed_econ_names = [get(FAMILY_DISPLAY_NAMES, f, f) for f in observed_econ_order]
println(" Observed: ", join(observed_econ_names, " < "))
# Cultural ordering
galtan_lookup = Dict(row.family => row.mean_galtan for row in eachrow(family_stats))
galtan_observed = [galtan_lookup[f] for f in EXPECTED_GALTAN_ORDER if haskey(galtan_lookup, f)]
galtan_expected_ranks = collect(1:length(galtan_observed))
galtan_observed_ranks = ordinalrank(galtan_observed)
rho_galtan = corspearman(Float64.(galtan_expected_ranks), Float64.(galtan_observed_ranks))
println(@sprintf(" Cultural ordering (6-family): Spearman ρ = %.3f", rho_galtan))
galtan_families_used = [f for f in EXPECTED_GALTAN_ORDER if haskey(galtan_lookup, f)]
galtan_names = [get(FAMILY_DISPLAY_NAMES, f, f) for f in galtan_families_used]
println(" Expected: ", join(galtan_names, " < "))
observed_galtan_order = galtan_families_used[sortperm(galtan_observed)]
observed_galtan_names = [get(FAMILY_DISPLAY_NAMES, f, f) for f in observed_galtan_order]
println(" Observed: ", join(observed_galtan_names, " < "))
println()
println("-"^60)
if rho_econ >= 0.9 && rho_galtan >= 0.8
println("EXCELLENT: Family means follow expected orderings on both dimensions")
elseif rho_econ >= 0.7 && rho_galtan >= 0.7
println("GOOD: Family means broadly follow expected orderings")
else
println("CONCERN: Review family ordering results")
end
return family_stats, rho_econ, rho_galtan
end
function validate_temporal_stability(model::DataFrame)
"""Check for implausible year-to-year position changes"""
println("\n" * "="^60)
println("TEMPORAL STABILITY VALIDATION")
println("="^60)
println("\nFlagging parties with >0.10 change per year")
println()
party_col = hasproperty(model, :party_id) ? :party_id : :party
# Compute year-to-year changes within each party
sort!(model, [party_col, :year])
unstable_parties = []
for party_df in groupby(model, party_col)
if nrow(party_df) < 2
continue
end
party_id = party_df[1, party_col]
country = party_df[1, :country]
# Compute differences
for dim in [:economic_lr, :galtan]
vals = party_df[!, dim]
years = party_df.year
for i in 2:length(vals)
diff = abs(vals[i] - vals[i-1])
year_gap = years[i] - years[i-1]
# Normalize by year gap (handle multi-year gaps)
annual_change = diff / max(year_gap, 1)
if annual_change > 0.10
push!(unstable_parties, (
party_id = party_id,
country = country,
dimension = string(dim),
year_from = years[i-1],
year_to = years[i],
val_from = vals[i-1],
val_to = vals[i],
change = diff,
annual_change = annual_change
))
end
end
end
end
if isempty(unstable_parties)
println(" No parties with >0.10 annual change found")
println(" EXCELLENT: Positions are temporally stable")
return DataFrame()
end
unstable_df = DataFrame(unstable_parties)
sort!(unstable_df, :annual_change, rev=true)
println(" Found $(nrow(unstable_df)) instances of rapid change:")
println()
@printf(" %-8s %-8s %-12s %-10s %-10s %8s\n",
"Party", "Country", "Dimension", "Years", "Change", "Annual")
println(" " * "-"^60)
for row in eachrow(unstable_df[1:min(20, nrow(unstable_df)), :])
@printf(" %-8d %-8s %-12s %d->%d %8.3f %8.3f\n",
row.party_id, row.country, row.dimension,
row.year_from, row.year_to, row.change, row.annual_change)
end
if nrow(unstable_df) > 20
println(" ... and $(nrow(unstable_df) - 20) more")
end
println()
println("-"^60)
n_parties = length(unique(unstable_df.party_id))
n_total = length(unique(model[!, party_col]))
println(@sprintf("Unstable parties: %d/%d (%.1f%%)", n_parties, n_total, 100*n_parties/n_total))
return unstable_df
end
function validate_position_distributions(model::DataFrame)
"""Check overall distribution of positions makes sense"""
println("\n" * "="^60)
println("POSITION DISTRIBUTION VALIDATION")
println("="^60)
println("\nSummary statistics for model estimates")
println()
for dim in [:economic_lr, :galtan]
if !hasproperty(model, dim)
continue
end
vals = model[!, dim]
println("$dim:")
println(@sprintf(" Mean: %.3f (should be ~0.50)", mean(vals)))
println(@sprintf(" Median: %.3f (should be ~0.50)", median(vals)))
println(@sprintf(" SD: %.3f (should be ~0.15)", std(vals)))
println(@sprintf(" Min: %.3f", minimum(vals)))
println(@sprintf(" Max: %.3f", maximum(vals)))
println(@sprintf(" Q25: %.3f", quantile(vals, 0.25)))
println(@sprintf(" Q75: %.3f", quantile(vals, 0.75)))
println()
end
# Check for extreme values
println("Extreme positions (< 0.10 or > 0.90):")
party_col = hasproperty(model, :party_id) ? :party_id : :party
for dim in [:economic_lr, :galtan]
if !hasproperty(model, dim)
continue
end
extreme = filter(row -> row[dim] < 0.10 || row[dim] > 0.90, model)
n_extreme = nrow(extreme)
pct_extreme = 100 * n_extreme / nrow(model)
println(@sprintf(" %s: %d (%.1f%%)", dim, n_extreme, pct_extreme))
if n_extreme > 0 && n_extreme <= 10
for row in eachrow(extreme[1:min(5, nrow(extreme)), :])
println(@sprintf(" Party %d (%s) %d: %.3f",
row[party_col], row.country, row.year, row[dim]))
end
end
end
end
function validate_country_patterns(model::DataFrame)
"""Check country-level patterns make sense"""
println("\n" * "="^60)
println("COUNTRY-LEVEL VALIDATION")
println("="^60)
println("\nMean positions by country (should vary but not wildly)")
println()
country_stats = combine(groupby(model, :country)) do df
DataFrame(
n_parties = length(unique(hasproperty(df, :party_id) ? df.party_id : df.party)),
n_obs = nrow(df),
mean_econ = mean(df.economic_lr),
mean_galtan = mean(df.galtan),
sd_econ = std(df.economic_lr),
sd_galtan = std(df.galtan)
)
end
sort!(country_stats, :n_obs, rev=true)
@printf("%-4s %6s %6s %8s %8s %8s %8s\n",
"CC", "Parties", "N", "Econ", "SD", "Galtan", "SD")
println("-"^60)
for row in eachrow(country_stats[1:min(20, nrow(country_stats)), :])
@printf("%-4s %6d %6d %8.3f %8.3f %8.3f %8.3f\n",
row.country, row.n_parties, row.n_obs,
row.mean_econ, row.sd_econ, row.mean_galtan, row.sd_galtan)
end
# Flag countries with unusual patterns
println("\nCountries with unusual patterns:")
unusual = filter(row -> row.mean_econ < 0.35 || row.mean_econ > 0.65 ||
row.mean_galtan < 0.35 || row.mean_galtan > 0.65, country_stats)
if nrow(unusual) == 0
println(" None - all countries have balanced party systems")
else
for row in eachrow(unusual)
issues = String[]
if row.mean_econ < 0.35
push!(issues, "left-skewed economy")
elseif row.mean_econ > 0.65
push!(issues, "right-skewed economy")
end
if row.mean_galtan < 0.35
push!(issues, "cultural-skewed")
elseif row.mean_galtan > 0.65
push!(issues, "TAN-skewed")
end
println(" $(row.country): $(join(issues, ", "))")
end
end
return country_stats
end
function save_construct_results(families::DataFrame, unstable::DataFrame,
countries::DataFrame, output_dir::String="outputs/checks")
"""Save construct validation results"""
if !isdir(output_dir)
mkpath(output_dir)
end
timestamp = Dates.format(now(), "yyyy-mm-dd_HH-MM-SS")
if nrow(families) > 0
families_file = joinpath(output_dir, "construct_families_$timestamp.csv")
CSV.write(families_file, families)
println("\nSaved: $families_file")
end
if nrow(unstable) > 0
unstable_file = joinpath(output_dir, "construct_unstable_$timestamp.csv")
CSV.write(unstable_file, unstable)
println("Saved: $unstable_file")
end
if nrow(countries) > 0
country_file = joinpath(output_dir, "construct_countries_$timestamp.csv")
CSV.write(country_file, countries)
println("Saved: $country_file")
end
end
# Main execution
function main()
println("="^60)
println("CONSTRUCT VALIDITY: Face Validity Checks")
println("="^60)
println("Checking if model estimates match expectations")
println()
# Load data
model, model_file = load_model_output()
println("\nModel output:")
println(" Rows: $(nrow(model))")
println(" Columns: $(names(model))")
# Run validations
families, rho_econ, rho_galtan = validate_party_families(model)
unstable = validate_temporal_stability(model)
validate_position_distributions(model)
countries = validate_country_patterns(model)
# Save results
save_construct_results(families, unstable, countries)
# Summary
println("\n" * "="^60)
println("CONSTRUCT VALIDITY SUMMARY")
println("="^60)
println(@sprintf(" Party family ordering:"))
println(@sprintf(" Economic (5-family): Spearman ρ = %.3f", rho_econ))
println(@sprintf(" Cultural (6-family): Spearman ρ = %.3f", rho_galtan))
n_unstable = nrow(unstable) > 0 ? length(unique(unstable.party_id)) : 0
party_col = hasproperty(model, :party_id) ? :party_id : :party
n_total = length(unique(model[!, party_col]))
println(@sprintf(" Temporal stability: %d/%d parties stable (>0.10/yr threshold)",
n_total - n_unstable, n_total))
if rho_econ >= 0.9 && rho_galtan >= 0.8 && n_unstable < 0.1 * n_total
println("\n EXCELLENT: Model has good construct validity")
elseif rho_econ >= 0.7 && rho_galtan >= 0.7
println("\n GOOD: Model has reasonable construct validity")
else
println("\n CONCERN: Review family ordering results")
end
println("\n" * "="^60)
println("VALIDATION COMPLETE")
println("="^60)
return (families=families, unstable=unstable, countries=countries)
end
if abspath(PROGRAM_FILE) == @__FILE__
main()
end
+533
View File
@@ -0,0 +1,533 @@
#!/usr/bin/env julia
#############################################################################
## validate_convergent.jl
## Convergent validity: Compare model estimates to external expert surveys
##
## Following Claassen (2019), this script computes:
## - Pearson/Spearman correlations between model and expert estimates
## - Fisher z-transformation for correlation confidence intervals
## - Mean Absolute Error (MAE) and Root Mean Square Error (RMSE)
## - Breakdown by survey project and decade
##
## Target: r > 0.8 with CHES (Claassen achieved 0.50-0.57)
#############################################################################
using CSV, DataFrames, Statistics, StatsBase, Dates, Printf, JSON
# Fisher z-transformation for correlation confidence intervals
fisher_z(r) = 0.5 * log((1 + r) / (1 - r))
fisher_z_inv(z) = (exp(2z) - 1) / (exp(2z) + 1)
function correlation_ci(r, n; alpha=0.05)
"""Calculate confidence interval for correlation using Fisher z-transformation"""
if n < 4
return (lower=NaN, upper=NaN)
end
z = fisher_z(r)
se = 1 / sqrt(n - 3)
z_crit = 1.96 # For 95% CI
z_lower = z - z_crit * se
z_upper = z + z_crit * se
return (lower=fisher_z_inv(z_lower), upper=fisher_z_inv(z_upper))
end
function load_model_output(base_dir::String=".")
"""Load the most recent 2D model party positions output"""
# First check for post_estimation output in root (current name, with legacy fallback)
position_files = filter(f -> startswith(f, "party_positions_") && endswith(f, ".csv") &&
!endswith(f, "_metadata.txt") && !endswith(f, "_tables.tex"), readdir(base_dir))
legacy_files = filter(f -> startswith(f, "party_positions_v1_") && endswith(f, ".csv"), readdir(base_dir))
append!(position_files, legacy_files)
if !isempty(position_files)
latest = sort(position_files)[end]
println("Loading model output: $latest")
return CSV.read(joinpath(base_dir, latest), DataFrame), latest
end
# Check current pipeline output directory, with legacy estimations/ fallback
for (label, est_dir) in [
("outputs/estimations/latest", joinpath(base_dir, "outputs", "estimations", "latest")),
("estimations", joinpath(base_dir, "estimations")),
]
if isdir(est_dir)
est_files = filter(f -> startswith(f, "party_positions_") && endswith(f, ".csv") &&
!endswith(f, "_metadata.txt") && !endswith(f, "_tables.tex"), readdir(est_dir))
if !isempty(est_files)
latest = sort(est_files)[end]
println("Loading model output: $label/$latest")
return CSV.read(joinpath(est_dir, latest), DataFrame), latest
end
end
end
error("No party_positions_*.csv found. Run 02_post_estimation.jl first.")
end
function load_expert_data(base_dir::String=".")
"""Load expert survey data files"""
data_dir = isfile(joinpath(base_dir, "expert.csv")) ? base_dir : joinpath(base_dir, "data")
# Load dimension-specific expert data
expert_file = joinpath(data_dir, "expert.csv")
if !isfile(expert_file)
error("expert.csv not found in $base_dir or $(joinpath(base_dir, "data"))")
end
println("Loading expert.csv...")
expert = CSV.read(expert_file, DataFrame)
println(" Rows: $(nrow(expert))")
println(" Variables: $(unique(expert.var))")
# Load L-R data
lr_file = joinpath(data_dir, "lr_data.csv")
if !isfile(lr_file)
error("lr_data.csv not found in $base_dir or $(joinpath(base_dir, "data"))")
end
println("Loading lr_data.csv...")
lr_data = CSV.read(lr_file, DataFrame)
println(" Rows: $(nrow(lr_data))")
println(" Variables: $(unique(lr_data.var))")
return expert, lr_data
end
function validate_economic_lr(model::DataFrame, expert::DataFrame)
"""Validate economic_lr against CHES/V-Party/POPPA/GPS lrecon"""
println("\n" * "="^60)
println("CONVERGENT VALIDITY: economic_lr")
println("="^60)
# Filter expert data for economic dimension
econ_vars = filter(v -> startswith(v, "lrecon_"), unique(expert.var))
econ_expert = filter(row -> row.var in econ_vars, expert)
println("\nExpert data variables: $(econ_vars)")
println("Expert observations: $(nrow(econ_expert))")
# Merge with model output
# Model has party_id column (from segment-based), expert has party column
if hasproperty(model, :party_id)
model_merge = select(model, :party_id => :party, :year, :economic_lr, :economic_lr_se)
else
model_merge = select(model, :party, :year, :economic_lr, :economic_lr_se)
end
merged = innerjoin(econ_expert, model_merge, on=[:party, :year])
println("Merged observations: $(nrow(merged))")
if nrow(merged) < 10
println("WARNING: Too few observations for meaningful validation")
return nothing
end
# Compute overall correlation
r_pearson = cor(merged.val, merged.economic_lr)
r_spearman = corspearman(merged.val, merged.economic_lr)
mae = mean(abs.(merged.val .- merged.economic_lr))
rmse = sqrt(mean((merged.val .- merged.economic_lr).^2))
ci = correlation_ci(r_pearson, nrow(merged))
println("\n--- Overall Statistics ---")
println(@sprintf(" Pearson r: %.4f [%.4f, %.4f]", r_pearson, ci.lower, ci.upper))
println(@sprintf(" Spearman r: %.4f", r_spearman))
println(@sprintf(" MAE: %.4f", mae))
println(@sprintf(" RMSE: %.4f", rmse))
println(@sprintf(" N: %d", nrow(merged)))
# Breakdown by project
println("\n--- By Project ---")
by_project = combine(groupby(merged, :project)) do df
n = nrow(df)
if n < 3
return DataFrame(n=n, r_pearson=NaN, mae=NaN)
end
DataFrame(
n = n,
r_pearson = cor(df.val, df.economic_lr),
r_spearman = corspearman(df.val, df.economic_lr),
mae = mean(abs.(df.val .- df.economic_lr)),
rmse = sqrt(mean((df.val .- df.economic_lr).^2))
)
end
for row in eachrow(sort(by_project, :n, rev=true))
if !isnan(row.r_pearson)
println(@sprintf(" %-10s: r=%.3f, MAE=%.3f, n=%d",
row.project, row.r_pearson, row.mae, row.n))
end
end
# Breakdown by decade
println("\n--- By Decade ---")
merged.decade = div.(merged.year, 10) .* 10
by_decade = combine(groupby(merged, :decade)) do df
n = nrow(df)
if n < 3
return DataFrame(n=n, r_pearson=NaN, mae=NaN)
end
DataFrame(
n = n,
r_pearson = cor(df.val, df.economic_lr),
mae = mean(abs.(df.val .- df.economic_lr))
)
end
for row in eachrow(sort(by_decade, :decade))
if !isnan(row.r_pearson)
println(@sprintf(" %ds: r=%.3f, MAE=%.3f, n=%d",
row.decade, row.r_pearson, row.mae, row.n))
end
end
return (
dimension = "economic_lr",
r_pearson = r_pearson,
r_spearman = r_spearman,
ci_lower = ci.lower,
ci_upper = ci.upper,
mae = mae,
rmse = rmse,
n = nrow(merged),
by_project = by_project,
by_decade = by_decade
)
end
function validate_galtan(model::DataFrame, expert::DataFrame)
"""Validate galtan against CHES galtan and V-Party/GPS libcons"""
println("\n" * "="^60)
println("CONVERGENT VALIDITY: galtan")
println("="^60)
# Filter expert data for GAL-TAN dimension
galtan_vars = filter(v -> occursin("galtan", v) || occursin("libcon", v), unique(expert.var))
galtan_expert = filter(row -> row.var in galtan_vars, expert)
println("\nExpert data variables: $(galtan_vars)")
println("Expert observations: $(nrow(galtan_expert))")
# Merge with model output
if hasproperty(model, :party_id)
model_merge = select(model, :party_id => :party, :year, :galtan, :galtan_se)
else
model_merge = select(model, :party, :year, :galtan, :galtan_se)
end
merged = innerjoin(galtan_expert, model_merge, on=[:party, :year])
println("Merged observations: $(nrow(merged))")
if nrow(merged) < 10
println("WARNING: Too few observations for meaningful validation")
return nothing
end
# Compute overall correlation
r_pearson = cor(merged.val, merged.galtan)
r_spearman = corspearman(merged.val, merged.galtan)
mae = mean(abs.(merged.val .- merged.galtan))
rmse = sqrt(mean((merged.val .- merged.galtan).^2))
ci = correlation_ci(r_pearson, nrow(merged))
println("\n--- Overall Statistics ---")
println(@sprintf(" Pearson r: %.4f [%.4f, %.4f]", r_pearson, ci.lower, ci.upper))
println(@sprintf(" Spearman r: %.4f", r_spearman))
println(@sprintf(" MAE: %.4f", mae))
println(@sprintf(" RMSE: %.4f", rmse))
println(@sprintf(" N: %d", nrow(merged)))
# Breakdown by project
println("\n--- By Project ---")
by_project = combine(groupby(merged, :project)) do df
n = nrow(df)
if n < 3
return DataFrame(n=n, r_pearson=NaN, mae=NaN)
end
DataFrame(
n = n,
r_pearson = cor(df.val, df.galtan),
r_spearman = corspearman(df.val, df.galtan),
mae = mean(abs.(df.val .- df.galtan)),
rmse = sqrt(mean((df.val .- df.galtan).^2))
)
end
for row in eachrow(sort(by_project, :n, rev=true))
if !isnan(row.r_pearson)
println(@sprintf(" %-10s: r=%.3f, MAE=%.3f, n=%d",
row.project, row.r_pearson, row.mae, row.n))
end
end
# Breakdown by decade
println("\n--- By Decade ---")
merged.decade = div.(merged.year, 10) .* 10
by_decade = combine(groupby(merged, :decade)) do df
n = nrow(df)
if n < 3
return DataFrame(n=n, r_pearson=NaN, mae=NaN)
end
DataFrame(
n = n,
r_pearson = cor(df.val, df.galtan),
mae = mean(abs.(df.val .- df.galtan))
)
end
for row in eachrow(sort(by_decade, :decade))
if !isnan(row.r_pearson)
println(@sprintf(" %ds: r=%.3f, MAE=%.3f, n=%d",
row.decade, row.r_pearson, row.mae, row.n))
end
end
return (
dimension = "galtan",
r_pearson = r_pearson,
r_spearman = r_spearman,
ci_lower = ci.lower,
ci_upper = ci.upper,
mae = mae,
rmse = rmse,
n = nrow(merged),
by_project = by_project,
by_decade = by_decade
)
end
function validate_discriminant(model::DataFrame, expert::DataFrame)
"""Compute cross-dimension correlations for discriminant validity (Campbell & Fiske 1959 MTMM)"""
println("\n" * "="^60)
println("DISCRIMINANT VALIDITY: Cross-dimension correlations")
println("="^60)
println("\nCampbell & Fiske (1959) MTMM framework:")
println(" Convergent: same dimension, different method → HIGH")
println(" Discriminant: different dimension, different method → LOW")
println()
# Get party column
if hasproperty(model, :party_id)
model_econ = select(model, :party_id => :party, :year, :economic_lr)
model_gal = select(model, :party_id => :party, :year, :galtan)
else
model_econ = select(model, :party, :year, :economic_lr)
model_gal = select(model, :party, :year, :galtan)
end
results = []
# 1. Expert economic vs Model economic (convergent - already computed, include for matrix)
econ_vars = filter(v -> startswith(v, "lrecon_"), unique(expert.var))
econ_expert = filter(row -> row.var in econ_vars, expert)
merged_ee = innerjoin(econ_expert, model_econ, on=[:party, :year])
if nrow(merged_ee) >= 10
r = cor(merged_ee.val, merged_ee.economic_lr)
push!(results, (model_dim="economic_lr", expert_dim="economic",
r_pearson=r, r_spearman=corspearman(merged_ee.val, merged_ee.economic_lr),
n=nrow(merged_ee), type="convergent"))
@printf(" Model Economic × Expert Economic: r = %.3f (convergent, n=%d)\n", r, nrow(merged_ee))
end
# 2. Expert economic vs Model galtan (discriminant)
merged_eg = innerjoin(econ_expert, model_gal, on=[:party, :year])
if nrow(merged_eg) >= 10
r = cor(merged_eg.val, merged_eg.galtan)
push!(results, (model_dim="galtan", expert_dim="economic",
r_pearson=r, r_spearman=corspearman(merged_eg.val, merged_eg.galtan),
n=nrow(merged_eg), type="discriminant"))
@printf(" Model GAL-TAN × Expert Economic: r = %.3f (discriminant, n=%d)\n", r, nrow(merged_eg))
end
# 3. Expert galtan vs Model galtan (convergent - already computed, include for matrix)
galtan_vars = filter(v -> occursin("galtan", v) || occursin("libcon", v), unique(expert.var))
galtan_expert = filter(row -> row.var in galtan_vars, expert)
merged_gg = innerjoin(galtan_expert, model_gal, on=[:party, :year])
if nrow(merged_gg) >= 10
r = cor(merged_gg.val, merged_gg.galtan)
push!(results, (model_dim="galtan", expert_dim="galtan",
r_pearson=r, r_spearman=corspearman(merged_gg.val, merged_gg.galtan),
n=nrow(merged_gg), type="convergent"))
@printf(" Model GAL-TAN × Expert GAL-TAN: r = %.3f (convergent, n=%d)\n", r, nrow(merged_gg))
end
# 4. Expert galtan vs Model economic (discriminant)
merged_ge = innerjoin(galtan_expert, model_econ, on=[:party, :year])
if nrow(merged_ge) >= 10
r = cor(merged_ge.val, merged_ge.economic_lr)
push!(results, (model_dim="economic_lr", expert_dim="galtan",
r_pearson=r, r_spearman=corspearman(merged_ge.val, merged_ge.economic_lr),
n=nrow(merged_ge), type="discriminant"))
@printf(" Model Economic × Expert GAL-TAN: r = %.3f (discriminant, n=%d)\n", r, nrow(merged_ge))
end
println()
println("MTMM Matrix:")
println(" Expert Economic Expert GAL-TAN")
for r in results
if r.model_dim == "economic_lr" && r.expert_dim == "economic"
@printf(" Model Economic: %.3f ", r.r_pearson)
end
end
for r in results
if r.model_dim == "economic_lr" && r.expert_dim == "galtan"
@printf("%.3f\n", r.r_pearson)
end
end
for r in results
if r.model_dim == "galtan" && r.expert_dim == "economic"
@printf(" Model GAL-TAN: %.3f ", r.r_pearson)
end
end
for r in results
if r.model_dim == "galtan" && r.expert_dim == "galtan"
@printf("%.3f\n", r.r_pearson)
end
end
return DataFrame(results)
end
function save_validation_results(results::Vector, output_dir::String="validation";
discriminant::Union{DataFrame, Nothing}=nothing)
"""Save validation results to CSV files"""
if !isdir(output_dir)
mkpath(output_dir)
end
timestamp = Dates.format(now(), "yyyy-mm-dd_HH-MM-SS")
# Summary table
summary_rows = []
for r in results
if r !== nothing
push!(summary_rows, (
dimension = r.dimension,
r_pearson = r.r_pearson,
r_spearman = r.r_spearman,
ci_lower = r.ci_lower,
ci_upper = r.ci_upper,
mae = r.mae,
rmse = r.rmse,
n = r.n
))
end
end
if !isempty(summary_rows)
summary_df = DataFrame(summary_rows)
summary_file = joinpath(output_dir, "convergent_summary_$timestamp.csv")
CSV.write(summary_file, summary_df)
println("\nSaved: $summary_file")
end
# By-project tables
for r in results
if r !== nothing && hasproperty(r, :by_project) && r.by_project !== nothing
project_file = joinpath(output_dir, "convergent_$(r.dimension)_by_project_$timestamp.csv")
CSV.write(project_file, r.by_project)
println("Saved: $project_file")
end
end
# By-decade tables
for r in results
if r !== nothing && hasproperty(r, :by_decade) && r.by_decade !== nothing
decade_file = joinpath(output_dir, "convergent_$(r.dimension)_by_decade_$timestamp.csv")
CSV.write(decade_file, r.by_decade)
println("Saved: $decade_file")
end
end
# Discriminant validity table
if discriminant !== nothing && nrow(discriminant) > 0
disc_file = joinpath(output_dir, "discriminant_summary_$timestamp.csv")
CSV.write(disc_file, discriminant)
println("Saved: $disc_file")
end
return summary_rows
end
function print_claassen_comparison(results::Vector)
"""Print comparison with Claassen (2019) benchmarks"""
println("\n" * "="^60)
println("COMPARISON WITH CLAASSEN (2019) BENCHMARKS")
println("="^60)
println("\nClaassen's results (mood estimates vs survey data):")
println(" Pearson r: 0.50-0.57")
println(" MAE: ~0.06 (6 pp on 0-1 scale)")
println()
println("Our target (party positions, should be HIGHER than mood):")
println(" Pearson r > 0.80 with expert surveys")
println(" MAE < 0.15 (reasonable measurement error)")
println()
println("-"^60)
@printf("%-15s %8s %8s %8s %8s\n", "Dimension", "r", "Target", "MAE", "Status")
println("-"^60)
for r in results
if r !== nothing
status = r.r_pearson > 0.80 ? "PASS" : (r.r_pearson > 0.70 ? "OK" : "LOW")
@printf("%-15s %8.3f %8s %8.3f %8s\n",
r.dimension, r.r_pearson, "> 0.80", r.mae, status)
end
end
println("-"^60)
end
# Main execution
function main()
println("="^60)
println("CONVERGENT VALIDITY: Model vs Expert Surveys")
println("="^60)
println("Following Claassen (2019) validation framework")
println()
# Load data
model, model_file = load_model_output()
expert, _ = load_expert_data()
println("\nModel output:")
println(" Rows: $(nrow(model))")
println(" Columns: $(names(model))")
# Run validations
results = []
push!(results, validate_economic_lr(model, expert))
push!(results, validate_galtan(model, expert))
# Run discriminant validity
discriminant = validate_discriminant(model, expert)
# Save results
save_validation_results(results; discriminant=discriminant)
# Print Claassen comparison
print_claassen_comparison(results)
println("\n" * "="^60)
println("VALIDATION COMPLETE")
println("="^60)
return results
end
if abspath(PROGRAM_FILE) == @__FILE__
main()
end
+375
View File
@@ -0,0 +1,375 @@
#!/usr/bin/env julia
#############################################################################
## validate_external.jl
## Out-of-sample validation via held-out expert observations
##
## Design:
## - Text data stays 100% intact (same parties, segments, indices)
## - 20% of expert/LR observations held out (stratified by source)
## - Expert-only parties (no text data) are never held out
## - Model trains on 80% expert + 100% text
## - Held-out expert ratings compared to model predictions
##
## Usage:
## julia scripts/validate_external.jl prepare
## julia 01_run_model.jl --data-dir validation/external_split/
## julia 02_post_estimation.jl # on training run
## julia scripts/validate_external.jl compute <model_positions.csv>
#############################################################################
using CSV, DataFrames, Statistics, Random, Dates, Printf
const HOLDOUT_FRAC = 0.20
const SEED = 42
# =========================================================================
# STEP 1: Prepare train/test split
# =========================================================================
function prepare_holdout_data(base_dir::String=".")
println("="^70)
println("PREPARING OUT-OF-SAMPLE VALIDATION SPLIT")
println("="^70)
println()
# Load data
text_data = CSV.read(joinpath(base_dir, "text_data.csv"), DataFrame)
expert = CSV.read(joinpath(base_dir, "expert.csv"), DataFrame)
lr_data = CSV.read(joinpath(base_dir, "lr_data.csv"), DataFrame)
println("Full dataset:")
println(" text_data: $(nrow(text_data)) rows, $(length(unique(text_data.party))) parties")
println(" expert: $(nrow(expert)) rows, $(length(unique(expert.party))) parties")
println(" lr_data: $(nrow(lr_data)) rows, $(length(unique(lr_data.party))) parties")
# Identify expert-only parties (no text data) — these are NEVER held out
text_parties = Set(unique(text_data.party))
expert_only_parties = Set(p for p in unique(vcat(expert.party, lr_data.party))
if !(p in text_parties))
println()
println("Expert-only parties (protected from holdout): $(length(expert_only_parties))")
# Split expert data: stratified by source variable
# Safeguard: ensure each party keeps at least one observation in training
Random.seed!(SEED)
expert.row_id = 1:nrow(expert)
expert.is_holdout = falses(nrow(expert))
for var_group in groupby(expert, :var)
var_name = first(var_group.var)
eligible = findall(row -> !(row.party in expert_only_parties), eachrow(var_group))
n_holdout = round(Int, length(eligible) * HOLDOUT_FRAC)
holdout_candidates = shuffle(eligible)
# Track per-party counts to ensure at least 1 stays in training
party_train_count = Dict{Int, Int}()
for idx in eligible
p = var_group.party[idx]
party_train_count[p] = get(party_train_count, p, 0) + 1
end
n_held = 0
for idx in holdout_candidates
n_held >= n_holdout && break
p = var_group.party[idx]
if party_train_count[p] > 1 # keep at least 1 in training
row_id = var_group.row_id[idx]
expert.is_holdout[row_id] = true
party_train_count[p] -= 1
n_held += 1
end
end
end
# Split LR data: stratified by source variable (same safeguard)
lr_data.row_id = 1:nrow(lr_data)
lr_data.is_holdout = falses(nrow(lr_data))
for var_group in groupby(lr_data, :var)
var_name = first(var_group.var)
eligible = findall(row -> !(row.party in expert_only_parties), eachrow(var_group))
n_holdout = round(Int, length(eligible) * HOLDOUT_FRAC)
holdout_candidates = shuffle(eligible)
party_train_count = Dict{Int, Int}()
for idx in eligible
p = var_group.party[idx]
party_train_count[p] = get(party_train_count, p, 0) + 1
end
n_held = 0
for idx in holdout_candidates
n_held >= n_holdout && break
p = var_group.party[idx]
if party_train_count[p] > 1
row_id = var_group.row_id[idx]
lr_data.is_holdout[row_id] = true
party_train_count[p] -= 1
n_held += 1
end
end
end
# Create train/test splits
expert_train = expert[.!expert.is_holdout, Not([:row_id, :is_holdout])]
expert_test = expert[expert.is_holdout, Not([:row_id, :is_holdout])]
lr_train = lr_data[.!lr_data.is_holdout, Not([:row_id, :is_holdout])]
lr_test = lr_data[lr_data.is_holdout, Not([:row_id, :is_holdout])]
# Report split
println()
println("Split summary ($(round(100*HOLDOUT_FRAC))% holdout):")
println(" expert train: $(nrow(expert_train)) rows ($(round(100*nrow(expert_train)/nrow(expert), digits=1))%)")
println(" expert test: $(nrow(expert_test)) rows ($(round(100*nrow(expert_test)/nrow(expert), digits=1))%)")
println(" lr train: $(nrow(lr_train)) rows ($(round(100*nrow(lr_train)/nrow(lr_data), digits=1))%)")
println(" lr test: $(nrow(lr_test)) rows ($(round(100*nrow(lr_test)/nrow(lr_data), digits=1))%)")
# Report per-source breakdown
println()
println("Per-source breakdown (expert):")
for var_name in sort(unique(expert.var))
n_full = count(expert.var .== var_name)
n_test = count(expert_test.var .== var_name)
println(" $var_name: $(n_full - n_test) train / $n_test test")
end
println()
println("Per-source breakdown (LR):")
for var_name in sort(unique(lr_data.var))
n_full = count(lr_data.var .== var_name)
n_test = count(lr_test.var .== var_name)
println(" $var_name: $(n_full - n_test) train / $n_test test")
end
# Verify: training set has same parties as full set
train_parties_expert = Set(unique(expert_train.party))
train_parties_lr = Set(unique(lr_train.party))
full_parties_expert = Set(unique(expert.party))
full_parties_lr = Set(unique(lr_data.party))
lost_expert = setdiff(full_parties_expert, train_parties_expert)
lost_lr = setdiff(full_parties_lr, train_parties_lr)
println()
if isempty(lost_expert) && isempty(lost_lr)
println("✓ No parties lost from training set")
else
println("⚠ Parties lost from expert training: $(length(lost_expert))")
println("⚠ Parties lost from LR training: $(length(lost_lr))")
end
# Save to output directory
# Files use standard names so 01_run_model.jl can load with --data-dir
output_dir = joinpath(base_dir, "validation", "external_split")
mkpath(output_dir)
# Training files (standard names for model loading)
CSV.write(joinpath(output_dir, "text_data.csv"), text_data) # UNCHANGED
CSV.write(joinpath(output_dir, "expert.csv"), expert_train)
CSV.write(joinpath(output_dir, "lr_data.csv"), lr_train)
# Test files (for compute step)
CSV.write(joinpath(output_dir, "expert_test.csv"), expert_test)
CSV.write(joinpath(output_dir, "lr_data_test.csv"), lr_test)
# Copy union mapping (needed by model)
if isdir(joinpath(base_dir, "data"))
mkpath(joinpath(output_dir, "data"))
cp(joinpath(base_dir, "data", "union_mapping.csv"),
joinpath(output_dir, "data", "union_mapping.csv"), force=true)
end
println()
println("Files saved to: $output_dir")
println(" text_data.csv — IDENTICAL to original ($(nrow(text_data)) rows)")
println(" expert.csv — training only ($(nrow(expert_train)) rows)")
println(" lr_data.csv — training only ($(nrow(lr_train)) rows)")
println(" expert_test.csv — held-out ($(nrow(expert_test)) rows)")
println(" lr_data_test.csv — held-out ($(nrow(lr_test)) rows)")
return output_dir
end
# =========================================================================
# STEP 2: Compute held-out validation metrics
# =========================================================================
function compute_holdout_metrics(model_file::String, test_dir::String)
println()
println("="^70)
println("COMPUTING HELD-OUT VALIDATION METRICS")
println("="^70)
# Load model output
model = CSV.read(model_file, DataFrame)
party_col = hasproperty(model, :party_id) ? :party_id : :party
println("Model output: $(nrow(model)) party-years")
# Load test data
expert_test = CSV.read(joinpath(test_dir, "expert_test.csv"), DataFrame)
lr_test = CSV.read(joinpath(test_dir, "lr_data_test.csv"), DataFrame)
println("Held-out expert: $(nrow(expert_test)) observations")
println("Held-out LR: $(nrow(lr_test)) observations")
# Build lookup: (party, year) → model estimates
model_lookup = Dict{Tuple{Int,Int}, NamedTuple}()
for row in eachrow(model)
key = (row[party_col], row.year)
model_lookup[key] = (
economic_lr = row.economic_lr,
galtan = row.galtan,
economic_lr_se = hasproperty(row, :economic_lr_se) ? row.economic_lr_se : missing,
galtan_se = hasproperty(row, :galtan_se) ? row.galtan_se : missing,
economic_lr_q025 = hasproperty(row, :economic_lr_q025) ? row.economic_lr_q025 : missing,
economic_lr_q975 = hasproperty(row, :economic_lr_q975) ? row.economic_lr_q975 : missing,
galtan_q025 = hasproperty(row, :galtan_q025) ? row.galtan_q025 : missing,
galtan_q975 = hasproperty(row, :galtan_q975) ? row.galtan_q975 : missing,
)
end
# Map expert variables to dimensions
econ_vars = Set(["lrecon_ches", "lrecon_poppa", "lrecon_gps", "lrecon_vparty", "welf_vparty"])
galtan_vars = Set(["galtan_ches", "libcon_gps", "immig_vparty", "lgbt_vparty",
"culsup_vparty", "relig_vparty", "gender_vparty"])
# Process expert test observations
results = NamedTuple[]
for row in eachrow(expert_test)
key = (row.party, row.year)
haskey(model_lookup, key) || continue
m = model_lookup[key]
if row.var in econ_vars
dim = "economic_lr"
model_val = m.economic_lr
model_q025 = m.economic_lr_q025
model_q975 = m.economic_lr_q975
elseif row.var in galtan_vars
dim = "galtan"
model_val = m.galtan
model_q025 = m.galtan_q025
model_q975 = m.galtan_q975
else
continue
end
covered = !ismissing(model_q025) && !ismissing(model_q975) &&
row.val >= model_q025 && row.val <= model_q975
push!(results, (
party = row.party,
country = row.country,
year = row.year,
var = row.var,
dimension = dim,
expert_val = row.val,
model_val = model_val,
error = row.val - model_val,
abs_error = abs(row.val - model_val),
covered_95 = ismissing(model_q025) ? missing : covered,
))
end
if isempty(results)
println("ERROR: No matching test observations found")
return nothing
end
results_df = DataFrame(results)
# Compute and report metrics
println()
println("-"^70)
println("HELD-OUT VALIDATION RESULTS")
println("-"^70)
# Overall
overall_r = cor(results_df.expert_val, results_df.model_val)
overall_mae = mean(results_df.abs_error)
overall_rmse = sqrt(mean(results_df.error .^ 2))
println()
println(@sprintf("Overall: r=%.4f, MAE=%.4f, RMSE=%.4f, n=%d",
overall_r, overall_mae, overall_rmse, nrow(results_df)))
# By dimension
println()
println("By dimension:")
for dim in sort(unique(results_df.dimension))
d = filter(r -> r.dimension == dim, results_df)
r_val = cor(d.expert_val, d.model_val)
mae = mean(d.abs_error)
rmse = sqrt(mean(d.error .^ 2))
cov = count(skipmissing(d.covered_95)) / count(!ismissing, d.covered_95)
println(@sprintf(" %-12s: r=%.4f, MAE=%.4f, RMSE=%.4f, CIC95=%.1f%%, n=%d",
dim, r_val, mae, rmse, 100*cov, nrow(d)))
end
# By source
println()
println("By source:")
for var in sort(unique(results_df.var))
v = filter(r -> r.var == var, results_df)
nrow(v) < 5 && continue
r_val = cor(v.expert_val, v.model_val)
mae = mean(v.abs_error)
println(@sprintf(" %-20s: r=%.4f, MAE=%.4f, n=%d", var, r_val, mae, nrow(v)))
end
return results_df
end
# =========================================================================
# Main
# =========================================================================
function main()
args = ARGS
if isempty(args) || args[1] == "prepare"
output_dir = prepare_holdout_data()
println()
println("="^70)
println("NEXT STEPS")
println("="^70)
println("""
1. Run model on training data:
julia 01_run_model.jl --data-dir $output_dir
2. Run post-estimation on training run output:
julia 02_post_estimation.jl
3. Compute held-out metrics:
julia scripts/validate_external.jl compute <party_positions_file.csv>
""")
elseif args[1] == "compute" && length(args) >= 2
model_file = args[2]
test_dir = joinpath("validation", "external_split")
isfile(model_file) || error("Model file not found: $model_file")
isdir(test_dir) || error("Test data not found. Run 'prepare' first.")
results_df = compute_holdout_metrics(model_file, test_dir)
if results_df !== nothing
timestamp = Dates.format(now(), "yyyy-mm-dd_HH-MM-SS")
output_file = joinpath("validation", "external_validation_$(timestamp).csv")
CSV.write(output_file, results_df)
println()
println("Detailed results saved: $output_file")
end
else
println("Usage:")
println(" julia scripts/validate_external.jl prepare")
println(" julia scripts/validate_external.jl compute <party_positions.csv>")
end
end
if abspath(PROGRAM_FILE) == @__FILE__
main()
end
+593
View File
@@ -0,0 +1,593 @@
#!/usr/bin/env julia
#############################################################################
## validate_uncertainty.jl
## Uncertainty validation: Posterior Predictive Coverage (PPC)
##
## Following Claassen (2019), this script computes:
## - PPC: What % of expert values fall within 95% posterior predictive interval?
## - Also computes 80% PPC for direct Claassen comparison (he reports 60.3%)
## - Wilson score CI for coverage proportion
## - Breakdown by survey source and decade
##
## Unlike credible interval coverage (which checks θ-CIs), posterior predictive
## coverage simulates what a new expert observation would look like given the
## model's beta likelihood, accounting for both position uncertainty AND
## measurement noise. Well-calibrated models should yield ~95% PPC at 95%.
##
## No model re-run needed: reads θ, γ, and φ from existing chain CSV files.
#############################################################################
using CSV, DataFrames, Statistics, Dates, Printf, Random, JSON
# =============================================================================
# Utility: Wilson score CI for a proportion
# =============================================================================
function wilson_ci(p, n; alpha=0.05)
if n == 0
return (lower=NaN, upper=NaN, se=NaN)
end
z = 1.96 # For 95% CI
denominator = 1 + z^2/n
center = (p + z^2/(2n)) / denominator
margin = z * sqrt((p*(1-p) + z^2/(4n))/n) / denominator
se = sqrt(p * (1-p) / n)
return (lower=center - margin, upper=center + margin, se=se)
end
# =============================================================================
# STEP 0: Find latest model run
# =============================================================================
function find_latest_run(base_dir::String="model_outputs")
if !isdir(base_dir)
error("Model outputs directory not found: $base_dir")
end
runs = filter(d -> startswith(d, "run_") && isdir(joinpath(base_dir, d)), readdir(base_dir))
if isempty(runs)
error("No runs found in $base_dir")
end
sort!(runs, rev=true)
latest = joinpath(base_dir, runs[1])
println("Using latest run: $latest")
return latest
end
# =============================================================================
# STEP 1: Load expert_dim.csv from model run data
# =============================================================================
function load_expert_dim(run_dir::String)
expert_dim_file = joinpath(run_dir, "data", "expert_dim.csv")
if !isfile(expert_dim_file)
error("expert_dim.csv not found in $run_dir/data/")
end
expert_dim = CSV.read(expert_dim_file, DataFrame)
println("Loaded expert_dim.csv: $(nrow(expert_dim)) observations")
println(" Unique rr values: $(length(unique(expert_dim.rr_exp_dim)))")
println(" Item indices (var_exp_dim): $(sort(unique(expert_dim.var_exp_dim)))")
println(" Dimensions (dim_idx_exp): $(sort(unique(expert_dim.dim_idx_exp)))")
return expert_dim
end
# =============================================================================
# STEP 2: Selectively load chain columns
# =============================================================================
function load_chains_selective(run_dir::String, needed_rr::Set{Int}, K::Int)
"""Load only the chain columns we need for posterior predictive checks."""
chains_dir = joinpath(run_dir, "chains")
chain_files = sort(filter(f -> endswith(f, ".csv") && startswith(f, "chain_"), readdir(chains_dir)))
if isempty(chain_files)
error("No chain files found in $chains_dir")
end
println("\nLoading $(length(chain_files)) chain files (selective columns)...")
# Build the set of column names we need
needed_cols = Set{String}()
# theta columns: economic_lr.{rr} and galtan.{rr} for each unique rr
for rr in needed_rr
push!(needed_cols, "economic_lr.$rr")
push!(needed_cols, "galtan.$rr")
end
# Item parameters: gamma_exp_intercept.1-K, gamma_exp_slope.1-K
for k in 1:K
push!(needed_cols, "gamma_exp_intercept.$k")
push!(needed_cols, "gamma_exp_slope.$k")
end
# Precision parameter
push!(needed_cols, "phi_exp_dim")
println(" Need $(length(needed_cols)) columns ($(length(needed_rr)) rr × 2 dims + $(2*K) item params + 1 phi)")
# Read the header from first chain to identify column indices
first_chain_path = joinpath(chains_dir, chain_files[1])
header_line = ""
open(first_chain_path) do f
for line in eachline(f)
if !startswith(line, "#")
header_line = line
break
end
end
end
all_cols = split(header_line, ",")
col_indices = Int[]
col_names = String[]
for (i, col) in enumerate(all_cols)
if col in needed_cols
push!(col_indices, i)
push!(col_names, col)
end
end
println(" Found $(length(col_indices))/$(length(needed_cols)) columns in chains")
if length(col_indices) < length(needed_cols)
missing_cols = setdiff(needed_cols, Set(col_names))
n_missing = length(missing_cols)
sample = collect(missing_cols)[1:min(5, n_missing)]
println(" WARNING: Missing columns (showing $( min(5, n_missing))/$n_missing): $sample")
end
# Build a type specification for selective reading
# We'll use CSV.read with select parameter
select_symbols = Symbol.(col_names)
all_chains = DataFrame[]
for (i, cf) in enumerate(chain_files)
path = joinpath(chains_dir, cf)
print(" Loading chain $i: $(cf)... ")
t = @elapsed begin
chain = CSV.read(path, DataFrame; comment="#", select=select_symbols)
end
println("$(nrow(chain)) samples, $(round(t, digits=1))s")
push!(all_chains, chain)
end
combined = vcat(all_chains...)
println("Combined: $(nrow(combined)) total posterior draws")
return combined
end
# =============================================================================
# STEP 3: Compute posterior predictive coverage
# =============================================================================
function compute_posterior_predictive_cic(chains::DataFrame, expert_dim::DataFrame;
ci_level::Float64=0.95, seed::Int=42)
"""
Compute posterior predictive coverage for expert dimension observations.
For each expert observation n with observed value y_n:
1. For each posterior draw s:
- Get theta_s = theta[dim, rr] (on logit scale, but chains store inv_logit)
- Compute mu_s = invlogit(gamma_intercept[k] + gamma_slope[k] * logit(theta_s))
- V4 (Beta): Draw y_pred_s ~ Beta(phi * mu_s, phi * (1 - mu_s))
- V5 (Beta-Binomial): Draw y_pred_s ~ Beta(phi * K * mu_s, phi * K * (1 - mu_s))
where K = n_experts for that observation
2. Compute quantile interval of y_pred draws
3. Check if y_n falls within interval
Returns DataFrame with one row per observation plus coverage indicator.
"""
rng = MersenneTwister(seed)
alpha_lower = (1 - ci_level) / 2
alpha_upper = 1 - alpha_lower
N = nrow(expert_dim)
S = nrow(chains) # total posterior draws
# Detect V5 (Beta-Binomial with K-scaling) by presence of n_experts column
has_k_scaling = hasproperty(expert_dim, :n_experts)
if has_k_scaling
k_vec = expert_dim.n_experts
println("\nV5 detected: using Beta(phi*K*mu, phi*K*(1-mu)) with per-observation K")
else
println("\nV4 detected: using Beta(phi*mu, phi*(1-mu))")
end
println("Computing posterior predictive coverage ($(round(Int, 100*ci_level))% level)")
println(" Expert observations: $N")
println(" Posterior draws: $S")
# Pre-extract phi vector
phi_vec = chains[!, :phi_exp_dim]
# Pre-extract gamma vectors for each item k
K = maximum(expert_dim.var_exp_dim)
gamma_int = Dict{Int, Vector{Float64}}()
gamma_slope = Dict{Int, Vector{Float64}}()
for k in 1:K
col_int = Symbol("gamma_exp_intercept.$k")
col_slope = Symbol("gamma_exp_slope.$k")
if hasproperty(chains, col_int) && hasproperty(chains, col_slope)
gamma_int[k] = chains[!, col_int]
gamma_slope[k] = chains[!, col_slope]
end
end
# Allocate result columns
covered = BitVector(undef, N)
pred_lower = Vector{Float64}(undef, N)
pred_upper = Vector{Float64}(undef, N)
pred_median = Vector{Float64}(undef, N)
# Pre-allocate per-observation draw buffer
y_pred = Vector{Float64}(undef, S)
prog_interval = max(1, N ÷ 20)
for n in 1:N
if n % prog_interval == 0 || n == N
pct = round(100 * n / N, digits=1)
print("\r Progress: $pct% ($n / $N)")
end
rr = expert_dim.rr_exp_dim[n]
dim = expert_dim.dim_idx_exp[n]
k = expert_dim.var_exp_dim[n]
y_obs = expert_dim.val[n]
# Get theta column (chains store inv_logit(theta), i.e. on [0,1] scale)
theta_col = dim == 1 ? Symbol("economic_lr.$rr") : Symbol("galtan.$rr")
if !hasproperty(chains, theta_col) || !haskey(gamma_int, k)
# Missing chain data — mark as not covered
covered[n] = false
pred_lower[n] = NaN
pred_upper[n] = NaN
pred_median[n] = NaN
continue
end
theta_star_vec = chains[!, theta_col] # inv_logit(theta), i.e. on [0,1]
g_int = gamma_int[k]
g_slope = gamma_slope[k]
# Effective concentration: phi for V4, phi * n_experts for V5
k_mult = has_k_scaling ? Float64(k_vec[n]) : 1.0
# For each posterior draw, simulate a predictive observation
for s in 1:S
theta_star = theta_star_vec[s]
# Convert back to latent scale for linear predictor
# theta_star is inv_logit(theta), so theta = logit(theta_star)
# Clamp to avoid Inf
theta_star_clamped = clamp(theta_star, 1e-10, 1 - 1e-10)
theta_latent = log(theta_star_clamped / (1 - theta_star_clamped))
# Linear predictor
lin = g_int[s] + g_slope[s] * theta_latent
# Mean of beta
mu = 1 / (1 + exp(-lin))
mu = clamp(mu, 1e-6, 1 - 1e-6)
# Beta parameters: phi * K * mu for V5, phi * mu for V4
phi = phi_vec[s] * k_mult
a = phi * mu
b = phi * (1 - mu)
# Draw from Beta(a, b) via gamma method (no Distributions.jl needed)
y_pred[s] = _rand_beta(rng, a, b)
end
# Compute predictive interval
sort!(y_pred)
idx_lo = max(1, round(Int, alpha_lower * S))
idx_hi = min(S, round(Int, alpha_upper * S))
idx_med = round(Int, 0.5 * S)
pred_lower[n] = y_pred[idx_lo]
pred_upper[n] = y_pred[idx_hi]
pred_median[n] = y_pred[idx_med]
covered[n] = (y_obs >= pred_lower[n]) && (y_obs <= pred_upper[n])
end
println() # newline after progress
# Add results to a copy of expert_dim
result = DataFrame(
rr = expert_dim.rr_exp_dim,
dim_idx = expert_dim.dim_idx_exp,
var_idx = expert_dim.var_exp_dim,
val = expert_dim.val,
party = expert_dim.party,
country = expert_dim.country,
year = expert_dim.year,
project = expert_dim.project,
var = expert_dim.var,
pred_lower = pred_lower,
pred_upper = pred_upper,
pred_median = pred_median,
covered = covered
)
return result
end
# =============================================================================
# Beta random variate without Distributions.jl
# =============================================================================
"""
_rand_beta(rng, a, b)
Generate a Beta(a, b) random variate using the Gamma method:
Beta(a,b) = X/(X+Y) where X ~ Gamma(a), Y ~ Gamma(b).
Uses Marsaglia & Tsang (2000) for Gamma generation.
"""
function _rand_beta(rng::AbstractRNG, a::Float64, b::Float64)
x = _rand_gamma(rng, a)
y = _rand_gamma(rng, b)
return x / (x + y)
end
"""
_rand_gamma(rng, shape)
Generate Gamma(shape, 1) random variate using Marsaglia & Tsang (2000).
For shape < 1, uses the rejection method with shape+1 then scales.
"""
function _rand_gamma(rng::AbstractRNG, shape::Float64)
if shape < 1.0
# Gamma(a) = Gamma(a+1) * U^(1/a) where U ~ Uniform(0,1)
return _rand_gamma(rng, shape + 1.0) * rand(rng)^(1.0 / shape)
end
# Marsaglia & Tsang (2000) for shape >= 1
d = shape - 1.0/3.0
c = 1.0 / sqrt(9.0 * d)
while true
local x::Float64
local v::Float64
while true
x = randn(rng)
v = 1.0 + c * x
if v > 0.0
break
end
end
v = v * v * v
u = rand(rng)
if u < 1.0 - 0.0331 * x^2 * x^2
return d * v
end
if log(u) < 0.5 * x^2 + d * (1.0 - v + log(v))
return d * v
end
end
end
# =============================================================================
# STEP 4: Summarize and save results
# =============================================================================
function summarize_coverage(result::DataFrame, ci_level::Float64)
level_pct = round(Int, 100 * ci_level)
println("\n" * "="^60)
println("POSTERIOR PREDICTIVE COVERAGE ($level_pct%)")
println("="^60)
# Overall by dimension
dim_names = Dict(1 => "economic_lr", 2 => "galtan")
summary_rows = []
for dim in sort(unique(result.dim_idx))
subset = filter(r -> r.dim_idx == dim, result)
n = nrow(subset)
n_covered = sum(subset.covered)
ppc = n_covered / n
ci = wilson_ci(ppc, n)
dim_name = dim_names[dim]
println(@sprintf("\n %-15s: %.1f%% [%.1f%%, %.1f%%] (%d/%d)",
dim_name, 100*ppc, 100*ci.lower, 100*ci.upper, n_covered, n))
push!(summary_rows, (
dimension = dim_name,
cic = ppc,
cic_pct = round(100 * ppc, digits=1),
ci_lower = ci.lower,
ci_upper = ci.upper,
n = n,
covered = n_covered
))
# By project
println("\n By survey source:")
by_project = combine(groupby(subset, :project)) do df
nc = sum(df.covered)
DataFrame(n = nrow(df), covered = nc, cic = nc / nrow(df))
end
sort!(by_project, :n, rev=true)
@printf(" %-12s %6s %8s\n", "Project", "N", "PPC")
for row in eachrow(by_project)
@printf(" %-12s %6d %7.1f%%\n", row.project, row.n, 100*row.cic)
end
# By decade
subset_with_decade = copy(subset)
subset_with_decade.decade = div.(subset_with_decade.year, 10) .* 10
println("\n By decade:")
by_decade = combine(groupby(subset_with_decade, :decade)) do df
nc = sum(df.covered)
DataFrame(n = nrow(df), covered = nc, cic = nc / nrow(df))
end
sort!(by_decade, :decade)
@printf(" %-8s %6s %8s\n", "Decade", "N", "PPC")
for row in eachrow(by_decade)
@printf(" %-8d %6d %7.1f%%\n", row.decade, row.n, 100*row.cic)
end
end
return summary_rows
end
function save_results(result_95::DataFrame, summary_95, summary_80,
by_project_95::Dict, output_dir::String="validation")
if !isdir(output_dir)
mkpath(output_dir)
end
timestamp = Dates.format(now(), "yyyy-mm-dd_HH-MM-SS")
# Summary table (95%)
if !isempty(summary_95)
summary_df = DataFrame(summary_95)
summary_file = joinpath(output_dir, "uncertainty_cic_summary_$timestamp.csv")
CSV.write(summary_file, summary_df)
println("\nSaved: $summary_file")
end
# Also save 80% summary
if !isempty(summary_80)
summary80_df = DataFrame(summary_80)
summary80_file = joinpath(output_dir, "uncertainty_cic_80pct_summary_$timestamp.csv")
CSV.write(summary80_file, summary80_df)
println("Saved: $summary80_file")
end
# By-project tables (95%)
dim_names = Dict(1 => "economic_lr", 2 => "galtan")
for (dim, bp) in by_project_95
project_file = joinpath(output_dir, "uncertainty_$(dim_names[dim])_by_project_$timestamp.csv")
CSV.write(project_file, bp)
println("Saved: $project_file")
end
return summary_95
end
function print_claassen_comparison(summary_95, summary_80)
println("\n" * "="^60)
println("COMPARISON WITH CLAASSEN (2019) BENCHMARKS")
println("="^60)
println("\nClaassen's result:")
println(" CIC (80% CI): 60.3%")
println(" (Using credible intervals for θ, not posterior predictive)")
println()
println("Our results (posterior predictive):")
println()
println("-"^60)
@printf("%-15s %10s %10s %8s\n", "Dimension", "PPC 95%", "PPC 80%", "Status")
println("-"^60)
dim_map_80 = Dict(r.dimension => r for r in summary_80)
for r in summary_95
ppc80 = haskey(dim_map_80, r.dimension) ? dim_map_80[r.dimension].cic : NaN
# Well-calibrated: 95% PPC should be near 95%
status = r.cic >= 0.90 ? "GOOD" : (r.cic >= 0.80 ? "OK" : "LOW")
@printf("%-15s %9.1f%% %9.1f%% %8s\n",
r.dimension, 100*r.cic, 100*ppc80, status)
end
println("-"^60)
println()
println("Interpretation:")
println(" 95% PPC ~95% = well-calibrated uncertainty")
println(" 80% PPC > 60% = exceeds Claassen (2019) benchmark")
end
# =============================================================================
# MAIN
# =============================================================================
function main()
println("="^60)
println("UNCERTAINTY VALIDATION: Posterior Predictive Coverage")
println("="^60)
println("Following Claassen (2019) validation framework")
println("Posterior predictive intervals account for both position")
println("uncertainty AND observation-level measurement noise.")
println()
# Step 0: Parse options and find run directory
run_dir = nothing
quick_mode = get(ENV, "QUICK_VALIDATION", "0") == "1"
for (i, arg) in enumerate(ARGS)
if arg == "--run-dir" && i < length(ARGS)
run_dir = ARGS[i + 1]
elseif startswith(arg, "--run-dir=")
run_dir = split(arg, "=", limit=2)[2]
elseif arg == "--quick"
quick_mode = true
end
end
if run_dir === nothing
run_dir = find_latest_run()
else
println("Using specified run directory: $run_dir")
end
# Step 1: Load expert_dim.csv
expert_dim = load_expert_dim(run_dir)
# Step 2: Selectively load chains
needed_rr = Set(expert_dim.rr_exp_dim)
K = maximum(expert_dim.var_exp_dim)
chains = load_chains_selective(run_dir, needed_rr, K)
# Step 3a: Compute 95% posterior predictive coverage
result_95 = compute_posterior_predictive_cic(chains, expert_dim; ci_level=0.95)
summary_95 = summarize_coverage(result_95, 0.95)
# Step 3b: Compute 80% posterior predictive coverage (Claassen benchmark)
# Recompute coverage from the same predictive draws but with 80% quantiles
if quick_mode
println("\nQUICK MODE: skipping 80% PPC recomputation")
summary_80 = [(dimension=r.dimension, n=r.n, covered=r.covered, cic=NaN, ci_level=0.80) for r in summary_95]
else
println("\n" * "="^60)
println("RECOMPUTING WITH 80% LEVEL (Claassen comparison)")
println("="^60)
result_80 = compute_posterior_predictive_cic(chains, expert_dim; ci_level=0.80, seed=42)
summary_80 = summarize_coverage(result_80, 0.80)
end
# Build by-project tables for 95%
dim_names = Dict(1 => "economic_lr", 2 => "galtan")
by_project_95 = Dict{Int, DataFrame}()
for dim in sort(unique(result_95.dim_idx))
subset = filter(r -> r.dim_idx == dim, result_95)
bp = combine(groupby(subset, :project)) do df
nc = sum(df.covered)
DataFrame(n = nrow(df), covered = nc, cic = nc / nrow(df))
end
sort!(bp, :n, rev=true)
by_project_95[dim] = bp
end
# Step 4: Save results
save_results(result_95, summary_95, summary_80, by_project_95)
# Step 5: Print Claassen comparison
print_claassen_comparison(summary_95, summary_80)
println("\n" * "="^60)
println("VALIDATION COMPLETE")
println("="^60)
return (summary_95=summary_95, summary_80=summary_80)
end
if abspath(PROGRAM_FILE) == @__FILE__
main()
end
+311
View File
@@ -0,0 +1,311 @@
# ============================================================
# 00_data-management.R - Master Data Pipeline Orchestrator
# ============================================================
# Coordinates all data processing sub-scripts and produces
# final output files for the 4D latent trait model.
#
# Sub-scripts (run conditionally based on intermediate file existence):
# 00a_process_manifesto.R -> manifesto_data.csv
# 00c_process_poldem.R -> poldem_data.csv
# 00d_process_expert.R -> expert_raw.csv, lr_data_raw.csv
# 00e_process_morgan.R -> morgan_data.csv, morgan_lr.csv
#
# Final outputs:
# text_data.csv - Combined manifesto + PolDem
# expert.csv - Expert survey data (CHES, V-Party, POPPA, GPS)
# lr_data.csv - General left-right anchoring data
# ============================================================
library(tidyverse)
library(countrycode)
# Set working directory (works both in RStudio and command line)
if (interactive() && requireNamespace("rstudioapi", quietly = TRUE)) {
try(setwd(dirname(rstudioapi::getActiveDocumentContext()$path)), silent = TRUE)
}
cat("============================================================\n")
cat("Data Management Pipeline\n")
cat("============================================================\n\n")
# ============================================================
# Configuration: Set to TRUE to force re-run of sub-scripts
# ============================================================
FORCE_RERUN_MANIFESTO <- FALSE
FORCE_RERUN_POLDEM <- FALSE
FORCE_RERUN_EXPERT <- FALSE
FORCE_RERUN_MORGAN <- FALSE
# ============================================================
# Step 1: Manifesto Data
# ============================================================
cat("Step 1: Manifesto data\n")
if (!file.exists("manifesto_data.csv") || !file.exists("election_data.csv") || FORCE_RERUN_MANIFESTO) {
cat(" Running 00a_process_manifesto.R...\n")
source("../src/r/00a_process_manifesto.R")
} else {
cat(" Loading cached manifesto_data.csv and election_data.csv...\n")
}
manifesto <- read_csv("manifesto_data.csv", show_col_types = FALSE)
election_data <- read_csv("election_data.csv", show_col_types = FALSE)
cat(sprintf(" Loaded manifesto: %d rows, %d parties\n", nrow(manifesto), n_distinct(manifesto$party)))
cat(sprintf(" Loaded election: %d rows, %d parties\n\n", nrow(election_data), n_distinct(election_data$party)))
# ============================================================
# Step 2: PolDem Media Data
# ============================================================
cat("Step 2: PolDem media data\n")
if (!file.exists("poldem_data.csv") || FORCE_RERUN_POLDEM) {
cat(" Running 00c_process_poldem.R...\n")
source("../src/r/00c_process_poldem.R")
} else {
cat(" Loading cached poldem_data.csv...\n")
}
poldem_data <- read_csv("poldem_data.csv", show_col_types = FALSE)
cat(sprintf(" Loaded: %d rows, %d parties\n\n", nrow(poldem_data), n_distinct(poldem_data$party)))
# ============================================================
# Step 4: Expert Survey Data
# ============================================================
cat("Step 3: Expert survey data\n")
if (!file.exists("expert_raw.csv") || !file.exists("lr_data_raw.csv") || FORCE_RERUN_EXPERT) {
cat(" Running 00d_process_expert.R...\n")
source("../src/r/00d_process_expert.R")
} else {
cat(" Loading cached expert_raw.csv and lr_data_raw.csv...\n")
}
expert_raw <- read_csv("expert_raw.csv", show_col_types = FALSE)
lr_data_raw <- read_csv("lr_data_raw.csv", show_col_types = FALSE)
cat(sprintf(" Expert: %d rows, LR: %d rows\n\n", nrow(expert_raw), nrow(lr_data_raw)))
# ============================================================
# Step 3b: Morgan (1976) Historical Expert Data
# ============================================================
cat("Step 3b: Morgan (1976) historical L-R data\n")
# First run to generate morgan_data.csv if needed
if (!file.exists("morgan_data.csv") || FORCE_RERUN_MORGAN) {
cat(" Running 00e_process_morgan.R (initial processing)...\n")
source("../src/r/00e_process_morgan.R")
}
# morgan_lr.csv depends on text_data.csv, so we need to check if it needs regeneration
# It will be generated/regenerated below after text_data is created
# ============================================================
# Step 4: Combine Text Data Sources
# ============================================================
cat("Step 4: Combining text data sources\n")
text_data <- bind_rows(manifesto, poldem_data)
cat(sprintf(" Combined text_data: %d rows\n", nrow(text_data)))
# Save unfiltered text_data for reproducible mismatch diagnosis
write_csv(text_data, "text_data_unfiltered.csv")
cat(sprintf(" Saved unfiltered text_data: %d rows, %d parties\n", nrow(text_data), n_distinct(text_data$party)))
# ============================================================
# Step 4b: Party Renames (applied before filtering)
# ============================================================
# Renames must happen BEFORE the relevance filter so that party IDs
# match across text_data and expert_raw when computing expert coverage.
# Simple renames only (organizational continuity: same leadership/members)
simple_renames <- c(
`10` = 1816L, # DE: Greens -> Bündnis90/Grüne
`276` = 120L, # RO: FDSN/PDSR -> PSD (renamed 2001)
`8054` = 878L, # IT: PDS -> DS (renamed 1998)
`1696` = 813L, # IT: MSI -> AN (refounded 1995)
`553` = 1968L, # BE: Vlaams Blok -> Vlaams Belang (refounded 2004)
`8058` = 1626L # IT: Forza Italia (refounded 2013) -> Forza Italia (same party, Berlusconi)
)
apply_simple_renames <- function(df) {
for (old_id in names(simple_renames)) {
df <- df %>%
mutate(party = ifelse(party == as.integer(old_id), simple_renames[[old_id]], party))
}
df
}
cat("\nStep 4b: Party renames\n")
text_data <- apply_simple_renames(text_data)
cat(sprintf(" Applied %d renames to text_data\n", length(simple_renames)))
# ============================================================
# Step 4c: Relevance Filter
# ============================================================
# Design: R pipeline filters for RELEVANCE (is this party worth modeling?).
# Julia pipeline handles INTERPOLATION QUALITY (MAX_GAP=7 segment splitting, MIN_OBS=2).
# Expert survey coverage is a relevance signal: CHES only covers parties with >1% vote share.
cat("\nStep 4c: Relevance filter\n")
parties_before <- n_distinct(text_data$party)
# Compute expert coverage per party (with renames applied for consistent matching)
expert_year_counts <- bind_rows(
expert_raw %>% select(party, year),
lr_data_raw %>% select(party, year)
) %>% distinct() %>%
apply_simple_renames() %>%
distinct() %>%
count(party, name = "expert_years")
expert_party_ids <- unique(expert_year_counts$party)
cat(sprintf(" Parties with expert data: %d\n", length(expert_party_ids)))
# Three-tier relevance filter:
# Tier 1: 3+ text data years (always include, regardless of expert data)
# Tier 2: 2 text years + any expert data (major newer parties like M5S, ANO, LREM)
# Tier 3: 1 text year + 3+ expert survey years (parties with rich expert coverage)
text_data <- text_data %>%
group_by(country, party) %>%
mutate(n_years = n_distinct(year)) %>%
ungroup() %>%
left_join(expert_year_counts, by = "party") %>%
mutate(expert_years = replace_na(expert_years, 0L)) %>%
mutate(
tier = case_when(
n_years >= 3 ~ 1L,
n_years >= 2 & party %in% expert_party_ids ~ 2L,
n_years >= 1 & expert_years >= 3 ~ 3L,
TRUE ~ 0L
)
) %>%
filter(tier > 0) %>%
select(-n_years, -expert_years, -tier)
parties_after <- n_distinct(text_data$party)
cat(sprintf(" Parties before filter: %d\n", parties_before))
cat(sprintf(" Parties after filter: %d\n", parties_after))
cat(sprintf(" Parties removed: %d\n\n", parties_before - parties_after))
# ============================================================
# Step 5: Party Harmonization
# ============================================================
cat("Step 5: Party harmonization (union-aware)\n")
# Load union mapping to identify constituent parties
union_map <- read_csv("union_mapping.csv", show_col_types = FALSE)
# Build set of constituent parties whose union is in text_data
constituent_parties <- union_map %>%
filter(manifesto_pf_id %in% unique(text_data$party)) %>%
pull(expert_pf_id)
cat(sprintf(" Union mappings loaded: %d rows covering %d unions\n",
nrow(union_map), n_distinct(union_map$manifesto_pf_id)))
cat(sprintf(" Constituent parties with unions in text_data: %d\n",
length(unique(constituent_parties))))
# Deduplicate union manifesto rows: where multiple CMP codes map to the same
# union PF ID with identical content, keep only one set per (party, year, var)
text_data_before_dedup <- nrow(text_data)
text_data <- text_data %>%
distinct(country, party, year, var, .keep_all = TRUE)
cat(sprintf(" Text data: %d unique parties after harmonization\n", n_distinct(text_data$party)))
cat(sprintf(" Text data: deduplicated %d -> %d rows\n", text_data_before_dedup, nrow(text_data)))
# Filter expert data: keep parties in text_data OR constituent parties of unions in text_data
expert <- expert_raw %>%
apply_simple_renames() %>%
group_by(country, party, var, year) %>%
summarise(
val = mean(val, na.rm = TRUE),
val_int = first(val_int),
n_scale = first(n_scale),
n_experts = first(n_experts),
project = first(project),
type_low = first(type_low),
type_high = first(type_high),
.groups = "drop"
) %>%
filter(party %in% unique(text_data$party) | party %in% constituent_parties)
lr_data <- lr_data_raw %>%
apply_simple_renames() %>%
group_by(country, party, var, year) %>%
summarise(
val = mean(val, na.rm = TRUE),
val_int = first(val_int),
n_scale = first(n_scale),
n_experts = first(n_experts),
project = first(project),
.groups = "drop"
) %>%
filter(party %in% unique(text_data$party) | party %in% constituent_parties)
cat(sprintf(" Expert data: %d rows (filtered to text_data parties)\n", nrow(expert)))
cat(sprintf(" LR data (CHES/POPPA): %d rows (filtered to text_data parties)\n", nrow(lr_data)))
# ============================================================
# Step 5b: Integrate Morgan L-R Data
# ============================================================
cat("\nStep 5b: Morgan L-R data integration\n")
# Generate morgan_lr.csv (requires text_data.csv to exist)
# We need to regenerate it if text_data changed or if forced
if (!file.exists("morgan_lr.csv") || FORCE_RERUN_MORGAN) {
cat(" Generating morgan_lr.csv...\n")
# Write text_data first so morgan script can use it
write_csv(text_data, "text_data.csv")
source("../src/r/00e_process_morgan.R")
}
# Load and integrate Morgan L-R data
if (file.exists("morgan_lr.csv")) {
morgan_lr <- read_csv("morgan_lr.csv", show_col_types = FALSE) %>%
apply_simple_renames() %>%
filter(party %in% unique(text_data$party) | party %in% constituent_parties)
cat(sprintf(" Morgan L-R: %d rows (filtered to text_data parties)\n", nrow(morgan_lr)))
cat(sprintf(" Morgan parties: %d\n", n_distinct(morgan_lr$party)))
cat(sprintf(" Morgan year range: %d-%d\n", min(morgan_lr$year), max(morgan_lr$year)))
# Combine with existing lr_data
lr_data_before <- nrow(lr_data)
lr_data <- bind_rows(lr_data, morgan_lr) %>%
arrange(country, party, year, var)
cat(sprintf(" Combined LR data: %d rows (+%d from Morgan)\n",
nrow(lr_data), nrow(lr_data) - lr_data_before))
} else {
cat(" Warning: morgan_lr.csv not found, skipping Morgan integration\n")
}
cat("\n")
# ============================================================
# Step 6: Write Final Outputs
# ============================================================
cat("Step 6: Writing final outputs\n")
write_csv(text_data, "text_data.csv")
write_csv(expert, "expert.csv")
write_csv(lr_data, "lr_data.csv")
cat("\n============================================================\n")
cat("Pipeline Complete!\n")
cat("============================================================\n\n")
cat("Output files written:\n")
cat(sprintf(" text_data.csv: %d rows\n", nrow(text_data)))
cat(sprintf(" - Manifesto: %d rows\n", sum(grepl("_manifesto", text_data$var))))
cat(sprintf(" - PolDem: %d rows\n", sum(grepl("_poldem", text_data$var))))
cat(sprintf(" expert.csv: %d rows\n", nrow(expert)))
cat(sprintf(" lr_data.csv: %d rows\n", nrow(lr_data)))
cat(sprintf(" - CHES: %d rows\n", sum(lr_data$var == "lr_ches")))
cat(sprintf(" - POPPA: %d rows\n", sum(lr_data$var == "lr_poppa")))
cat(sprintf(" - Morgan: %d rows\n", sum(lr_data$var == "lr_morgan")))
cat("\nUnique parties in text_data:", n_distinct(text_data$party), "\n")
cat("Countries:", paste(sort(unique(text_data$country)), collapse = ", "), "\n")
cat("Year range:", min(text_data$year, na.rm = TRUE), "-", max(text_data$year, na.rm = TRUE), "\n")
+178
View File
@@ -0,0 +1,178 @@
# ============================================================
# 00a_process_manifesto.R - Manifesto Project Data Processing
# ============================================================
# Processes Manifesto Project data for the 4D latent trait model
# Input: $PARTY2D_RAW_DATA_DIR/manifesto/MPDataset_MPDS2025a.csv
# Output: manifesto_data.csv
# ============================================================
library(tidyverse)
library(countrycode)
library(purrr)
# Set working directory (works both in RStudio and command line)
if (interactive() && requireNamespace("rstudioapi", quietly = TRUE)) {
try(setwd(dirname(rstudioapi::getActiveDocumentContext()$path)), silent = TRUE)
}
cat("Processing Manifesto Project data...\n")
raw_data_dir <- Sys.getenv(
"PARTY2D_RAW_DATA_DIR",
unset = file.path("..", "..", "_local", "raw")
)
manifesto_raw_path <- file.path(raw_data_dir, "manifesto", "MPDataset_MPDS2025a.csv")
# ============================================================
# PartyFacts Linkage
# ============================================================
partyfacts_raw <- read_csv('partyfacts-external-parties.csv', show_col_types = FALSE)
manifesto_link <- partyfacts_raw %>%
filter(dataset_key == "manifesto") %>%
transmute(id = dataset_party_id,
country = countrycode(country, origin = 'iso3c', destination = "iso2c"),
party = partyfacts_id,
party = ifelse(party == 622, 604, party))
# ============================================================
# Load Manifesto Data
# ============================================================
manifesto_data <- read_csv(manifesto_raw_path, show_col_types = FALSE)
# ============================================================
# CMP Code Mapping to 4 Dimensions
# ============================================================
vars <- tribble(
~type, ~subtype, ~per_var, ~stance, ~label,
# pro_market
"pro_market", "Market Regulation", "per401", "Positive", "Free Market Economy",
"pro_market", "Economic Liberalization","per402", "Positive", "Incentives: Positive",
"pro_market", "Market Regulation", "per407", "Positive", "Protectionism: Negative",
"pro_market", "Economic Liberalization","per414", "Positive", "Economic Orthodoxy",
"pro_market", "Economic Liberalization","per505", "Positive", "Welfare State Limitation",
"pro_market", "Economic Liberalization","per507", "Positive", "Education Limitation",
"pro_market", "Economic Liberalization","per702", "Positive", "Labour Groups: Negative",
"pro_market", "Market Regulation", "per406", "Negative", "Protectionism: Positive",
"pro_market", "Market Regulation", "per412", "Negative", "Controlled Economy",
"pro_market", "Economic Liberalization","per504", "Negative", "Welfare State Expansion",
# pro_welfare
"pro_welfare", "Economic Intervention", "per403", "Positive", "Market Regulation",
"pro_welfare", "Economic Intervention", "per404", "Positive", "Economic Planning",
"pro_welfare", "Economic Intervention", "per412", "Positive", "Controlled Economy",
"pro_welfare", "Economic Intervention", "per413", "Positive", "Nationalisation",
"pro_welfare", "Social Services", "per504", "Positive", "Welfare State Expansion",
"pro_welfare", "Social Services", "per506", "Positive", "Education Expansion",
"pro_welfare", "Economic Intervention", "per701", "Positive", "Labour Groups: Positive",
"pro_welfare", "Economic Intervention", "per401", "Negative", "Free Market Economy",
"pro_welfare", "Social Services", "per505", "Negative", "Welfare State Limitation",
# cosmopolitan
"cosmopolitan", "Internationalism", "per107", "Positive", "Internationalism: Positive",
"cosmopolitan", "Internationalism", "per108", "Positive", "European Community/Union: Positive",
"cosmopolitan", "Multiculturalism", "per607", "Positive", "Multiculturalism: Positive",
"cosmopolitan", "Multiculturalism", "per201", "Positive", "Freedom and Human Rights",
"cosmopolitan", "Multiculturalism", "per604", "Positive", "traditional Morality: Negative",
"cosmopolitan", "Internationalism", "per109", "Negative", "Internationalism: Negative",
"cosmopolitan", "Multiculturalism", "per601", "Negative", "National Way of Life: Positive",
# traditional
"traditional", "National Identity", "per109", "Positive", "Internationalism: Negative",
"traditional", "Conservative Morality", "per110", "Positive", "European Community/Union: Negative",
"traditional", "National Identity", "per601", "Positive", "National Way of Life: Positive",
"traditional", "Conservative Morality", "per603", "Positive", "traditional Morality: Positive",
"traditional", "Conservative Morality", "per608", "Positive", "Multiculturalism: Negative",
"traditional", "Conservative Morality", "per605", "Positive", "Law and Order: Positive",
"traditional", "National Identity", "per107", "Negative", "Internationalism: Positive",
"traditional", "Conservative Morality", "per607", "Negative", "Multiculturalism: Positive"
)
# ============================================================
# Process Manifesto Data
# ============================================================
manifesto <- vars %>%
pmap_dfr(~ manifesto_data %>%
transmute(country = countrycode(countryname, origin = 'country.name', destination = 'iso2c'),
year = as.numeric(format(as.Date(edate, format = "%d/%m/%Y"), "%Y")),
id = as.character(party),
count = round(.data[[..3]]),
var = ..3,
label = ..5,
type = ..1,
subtype = ..2,
stance = ..4,
project = 'Manifesto Project') %>%
left_join(manifesto_link, by = c("id", "country")) %>%
select(-id)) %>%
group_by(party, country, year, subtype) %>%
summarise(
positive = sum(count[stance == "Positive"], na.rm = TRUE),
sample = sum(count, na.rm = TRUE),
type = first(type),
project = first(project),
.groups = "drop"
) %>%
na.omit() %>%
rename(var = subtype) %>%
# Convert to bipolar bridge structure (type_high/type_low)
mutate(
type_high = case_when(
type == "pro_welfare" ~ "pro_welfare",
type == "pro_market" ~ "pro_market",
type == "cosmopolitan" ~ "cosmopolitan",
type == "traditional" ~ "traditional"
),
type_low = case_when(
type %in% c("pro_welfare", "pro_market") ~ ifelse(type == "pro_welfare", "pro_market", "pro_welfare"),
type %in% c("cosmopolitan", "traditional") ~ ifelse(type == "cosmopolitan", "traditional", "cosmopolitan")
)
) %>%
select(-type) %>%
# Add _manifesto suffix to variable names
mutate(var = paste0(tolower(gsub(" ", "_", var)), "_manifesto"))
# ============================================================
# NOTE: Temporal continuity filter moved to 00_data-management.R
# This allows exempting parties that appear in parliamentary data
# (parties in parliament are by definition not fringe parties)
# ============================================================
cat("Skipping temporal filter (applied in 00_data-management.R after combining with parl data)\n")
cat(sprintf(" Parties: %d\n", n_distinct(manifesto$party)))
# ============================================================
# Write Output
# ============================================================
write_csv(manifesto, "manifesto_data.csv")
cat(sprintf("Output: manifesto_data.csv (%d rows, %d parties)\n",
nrow(manifesto), n_distinct(manifesto$party)))
# ============================================================
# Election Data Extraction (vote shares)
# ============================================================
cat("\nExtracting election data (pervote)...\n")
election_data <- manifesto_data %>%
transmute(
country = countrycode(countryname, origin = 'country.name', destination = 'iso2c'),
year = as.numeric(format(as.Date(edate, format = "%d/%m/%Y"), "%Y")),
id = as.character(party),
pervote = pervote
) %>%
left_join(manifesto_link, by = c("id", "country")) %>%
select(-id) %>%
filter(!is.na(party), !is.na(pervote)) %>%
# Keep one row per (party, country, year) — take max pervote if duplicates
group_by(party, country, year) %>%
summarise(pervote = max(pervote, na.rm = TRUE), .groups = "drop") %>%
arrange(country, party, year)
write_csv(election_data, "election_data.csv")
cat(sprintf("Output: election_data.csv (%d rows, %d parties)\n",
nrow(election_data), n_distinct(election_data$party)))
# Export manifesto_link for use by other scripts
# (poldem also needs it for CMP linkage)
+161
View File
@@ -0,0 +1,161 @@
# ============================================================
# 00c_process_poldem.R - PolDem Media Data Processing
# ============================================================
# Processes PolDem (Political Deliberation in the Media) data
# for the 4D latent trait model
#
# Input: $PARTY2D_RAW_DATA_DIR/poldem/poldem-election_all.csv (sentence-level)
# Output: poldem_data.csv (party-year-var aggregates)
# ============================================================
library(tidyverse)
library(countrycode)
# Set working directory (works both in RStudio and command line)
if (interactive() && requireNamespace("rstudioapi", quietly = TRUE)) {
try(setwd(dirname(rstudioapi::getActiveDocumentContext()$path)), silent = TRUE)
}
cat("Processing PolDem media data...\n")
raw_data_dir <- Sys.getenv(
"PARTY2D_RAW_DATA_DIR",
unset = file.path("..", "..", "_local", "raw")
)
poldem_raw_path <- file.path(raw_data_dir, "poldem", "poldem-election_all.csv")
# ============================================================
# PartyFacts Linkage (via CMP party IDs)
# ============================================================
partyfacts_raw <- read_csv('partyfacts-external-parties.csv', show_col_types = FALSE)
manifesto_link <- partyfacts_raw %>%
filter(dataset_key == "manifesto") %>%
transmute(cmp = as.numeric(dataset_party_id), # Convert to numeric for join
country_pf = countrycode(country, origin = 'iso3c', destination = "iso2c"),
party = partyfacts_id,
party = ifelse(party == 622, 604, party))
# ============================================================
# Issue Category Mapping to 4 Dimensions
# ============================================================
# For positive direction: type_high is the active trait
# For negative direction: we flip (same data, just contributes to the opposite trait)
poldem_mapping <- tribble(
~issue_cat, ~dimension, ~type_high, ~type_low,
# Economic dimension
"ecolib", "economic", "pro_market", "pro_welfare", # Economic liberalization
"welfare", "economic", "pro_welfare", "pro_market", # Welfare state
# Final exclusion: the PolDem economic-reform category is intentionally
# omitted because item-response diagnostics showed that it did not load
# substantively onto the economic latent trait.
# Cultural dimension
"immig", "cultural", "cosmopolitan", "traditional", # Immigration (pro = cosmopolitan)
"cultlib", "cultural", "cosmopolitan", "traditional", # Cultural liberalism
"nationalism", "cultural", "traditional", "cosmopolitan", # Nationalism (pro = traditional)
"europe", "cultural", "cosmopolitan", "traditional", # EU integration (pro = cosmopolitan)
"euro", "cultural", "cosmopolitan", "traditional", # Euro currency (pro = cosmopolitan)
"defense", "cultural", "traditional", "cosmopolitan", # Defense (pro = traditional)
"security", "cultural", "traditional", "cosmopolitan" # Security/law-order (pro = traditional)
)
cat(sprintf(" Using %d issue categories\n", nrow(poldem_mapping)))
# ============================================================
# Load and Clean PolDem Data
# ============================================================
poldem_raw <- read_csv(poldem_raw_path, show_col_types = FALSE)
cat(sprintf(" Raw PolDem data: %d rows\n", nrow(poldem_raw)))
poldem <- poldem_raw %>%
# Fix country codes
mutate(country = case_when(
iso2code == "AU" ~ "AT", # Austria (PolDem uses AU instead of AT)
iso2code == "UK" ~ "GB", # United Kingdom
TRUE ~ iso2code
)) %>%
# Extract year from article date (format: YYYY-MM-DD)
mutate(year = suppressWarnings(as.numeric(substr(date_art, 1, 4)))) %>%
# Filter to valid issue categories only
filter(issue_cat %in% poldem_mapping$issue_cat) %>%
# Convert direction to numeric and filter out neutral/NA
mutate(direction = as.numeric(direction)) %>%
filter(!is.na(direction) & direction != 0) %>%
# Remove rows with invalid years
filter(!is.na(year))
cat(sprintf(" After filtering: %d rows (valid issues, non-neutral)\n", nrow(poldem)))
# ============================================================
# Link to PartyFacts via CMP codes
# ============================================================
poldem <- poldem %>%
mutate(cmp = as.numeric(cmp)) %>%
left_join(manifesto_link, by = "cmp") %>%
filter(!is.na(party))
# Report linkage
n_linked <- nrow(poldem)
n_unlinked <- nrow(poldem_raw %>%
filter(issue_cat %in% poldem_mapping$issue_cat) %>%
mutate(direction = as.numeric(direction)) %>%
filter(!is.na(direction) & direction != 0)) - n_linked
cat(sprintf(" Linked to PartyFacts: %d rows\n", n_linked))
if (n_unlinked > 0) {
cat(sprintf(" Warning: %d rows could not be linked (missing CMP mapping)\n", n_unlinked))
}
# ============================================================
# Aggregate to Party-Year-Issue Level
# Using round(sum()) for weak direction values (0.5, -0.5)
# ============================================================
poldem_agg <- poldem %>%
left_join(poldem_mapping, by = "issue_cat") %>%
group_by(party, country, year, issue_cat, type_high, type_low) %>%
summarise(
# Sum positive directions (0.5 and 1), then round
positive = round(sum(direction[direction > 0])),
# Sum absolute directions for sample (all non-neutral), then round
sample = round(sum(abs(direction))),
n_obs = n(),
.groups = "drop"
) %>%
# Minimum 3 observations per group
filter(n_obs >= 3) %>%
select(-n_obs)
cat(sprintf(" After aggregation: %d party-year-issue observations\n", nrow(poldem_agg)))
# ============================================================
# Format Output (matching manifesto structure)
# ============================================================
poldem_data <- poldem_agg %>%
mutate(
var = paste0(issue_cat, "_poldem"),
project = "PolDem"
) %>%
select(party, country, year, var, positive, sample, type_high, type_low, project)
# ============================================================
# Write Output
# ============================================================
write_csv(poldem_data, "poldem_data.csv")
cat(sprintf("\nOutput: poldem_data.csv\n"))
cat(sprintf(" Total rows: %d\n", nrow(poldem_data)))
cat(sprintf(" Unique parties: %d\n", n_distinct(poldem_data$party)))
cat(sprintf(" Countries: %s\n", paste(sort(unique(poldem_data$country)), collapse = ", ")))
cat(sprintf(" Year range: %d-%d\n", min(poldem_data$year, na.rm = TRUE), max(poldem_data$year, na.rm = TRUE)))
cat("\n Rows by issue category:\n")
poldem_data %>%
group_by(var) %>%
summarise(n = n(), .groups = "drop") %>%
arrange(desc(n)) %>%
print()
+580
View File
@@ -0,0 +1,580 @@
# ============================================================
# 00d_process_expert.R - Expert Survey Data Processing
# ============================================================
# Processes expert survey data from multiple sources:
# - Chapel Hill Expert Survey (CHES)
# - V-Party Dataset
# - POPPA
# - GPS (Norris)
#
# Outputs: expert_raw.csv, lr_data_raw.csv
#
# V5 changes:
# - val_int (integer rounded to nearest scale point) and n_scale columns
# - n_experts column preserved (not dropped)
# - V-Party cultural expansion: 5 native items replace GPS ep_v6_lib_cons
# - V-Party economic expansion: v2pawelf added
# - Reverse-coding for V-Party cultural + welfare items
# ============================================================
library(tidyverse)
library(countrycode)
library(haven)
library(foreign)
# Set working directory (works both in RStudio and command line)
if (interactive() && requireNamespace("rstudioapi", quietly = TRUE)) {
try(setwd(dirname(rstudioapi::getActiveDocumentContext()$path)), silent = TRUE)
}
cat("Processing expert survey data...\n")
# ============================================================
# PartyFacts Linkage for CHES
# ============================================================
partyfacts_raw <- read_csv('partyfacts-external-parties.csv', show_col_types = FALSE)
ches_link <- partyfacts_raw %>%
filter(dataset_key == "ches") %>%
transmute(id = dataset_party_id,
country = countrycode(country, origin = 'iso3c', destination = "iso2c"),
party = partyfacts_id)
# ============================================================
# Expert Count Tables (from individual response files)
# ============================================================
cat(" Loading expert count tables from individual response files...\n")
# CHES 2024: dual lookup (party_id primary, country+name fallback for ID mismatches)
ches24_exp_raw <- read_csv('~/data/ches/CHES_2024_expert_level.csv', show_col_types = FALSE)
ches24_exp_by_id <- ches24_exp_raw %>%
group_by(party_id) %>%
summarise(n_experts_id = as.integer(n_distinct(id)), .groups = "drop")
ches24_exp_by_name <- ches24_exp_raw %>%
mutate(country_iso2 = countrycode(cname, origin = "country.name", destination = "iso2c")) %>%
group_by(country_iso2, party_name) %>%
summarise(n_experts_name = as.integer(n_distinct(id)), .groups = "drop")
ches_ca_expert_counts <- read_csv('~/data/ches/CHES_CA2023_expert_level.csv', show_col_types = FALSE) %>%
group_by(party_id) %>%
summarise(n_experts = as.integer(n_distinct(expert)), .groups = "drop")
ches_la_expert_counts <- read_csv('~/data/ches/CHES_LA2020_expert_level.csv', show_col_types = FALSE) %>%
group_by(party_id) %>%
summarise(n_experts = as.integer(n_distinct(expert_id)), .groups = "drop")
ches_il_expert_counts <- read_csv('~/data/ches/CHES_IL_expert_level.csv', show_col_types = FALSE) %>%
group_by(party_id, year) %>%
summarise(n_experts = as.integer(n_distinct(id)), .groups = "drop")
# ============================================================
# Chapel Hill Expert Survey (CHES) - 1999-2019
# ============================================================
cat(" Processing CHES 1999-2019...\n")
ches <- read_csv('~/data/ches/1999-2019_CHES_dataset_means(v3).csv', show_col_types = FALSE) %>%
rename(country_id = country) %>%
left_join(readRDS('~/data/ches/link.rds'), by = "country_id") %>%
transmute(country = countrycode(country, origin = 'country.name', destination = 'iso2c'),
vote = vote,
year = year,
id = as.character(party_id),
project = 'CHES',
n_experts = as.integer(expert),
lrecon_ches = lrecon/10,
galtan_ches = galtan/10) %>%
pivot_longer(cols = lrecon_ches:galtan_ches, names_to = 'var', values_to = 'val') %>%
mutate(n_scale = 10L) %>%
left_join(ches_link, by = c("id", "country")) %>%
filter(!is.na(val), !is.na(party), !is.na(country)) %>%
select(-id) %>%
mutate(type_low = ifelse(var == "lrecon_ches", "pro_welfare", "cosmopolitan"),
type_high = ifelse(var == "lrecon_ches", "pro_market", "traditional"))
# ============================================================
# CHES 2024 Update
# ============================================================
cat(" Processing CHES 2024...\n")
# Country code lookup for CHES 2024 format
country_lookup <- c(
"be" = "BE", "dk" = "DK", "ge" = "DE", "gr" = "GR", "esp" = "ES",
"fr" = "FR", "irl" = "IE", "it" = "IT", "nl" = "NL", "uk" = "GB",
"por" = "PT", "aus" = "AT", "fin" = "FI", "sv" = "SE", "bul" = "BG",
"cz" = "CZ", "est" = "EE", "hun" = "HU", "lat" = "LV", "lith" = "LT",
"pol" = "PL", "rom" = "RO", "slo" = "SK", "sle" = "SI", "cro" = "HR",
"tur" = "TR", "nor" = "NO", "swi" = "CH", "mal" = "MT", "cyp" = "CY",
"ice" = "IS"
)
convert_country_codes <- function(codes) {
result <- country_lookup[codes]
result[is.na(result)] <- codes[is.na(result)]
return(unname(result))
}
ches24 <- read_csv('~/data/ches/CHES_2024_final_v2.csv', show_col_types = FALSE) %>%
mutate(country_iso2 = convert_country_codes(country)) %>%
left_join(ches24_exp_by_id, by = "party_id") %>%
left_join(ches24_exp_by_name, by = c("country_iso2", "party" = "party_name")) %>%
transmute(country = country_iso2,
vote = vote,
year = 2024,
id = as.character(party_id),
project = 'CHES',
n_experts = coalesce(n_experts_id, n_experts_name),
lrecon_ches = lrecon/10,
galtan_ches = galtan/10) %>%
pivot_longer(cols = lrecon_ches:galtan_ches, names_to = 'var', values_to = 'val') %>%
mutate(n_scale = 10L) %>%
left_join(ches_link, by = c("id", "country")) %>%
filter(!is.na(val), !is.na(party), !is.na(country)) %>%
select(-id) %>%
mutate(type_low = ifelse(var == "lrecon_ches", "pro_welfare", "cosmopolitan"),
type_high = ifelse(var == "lrecon_ches", "pro_market", "traditional"))
ches <- bind_rows(ches, ches24)
# ============================================================
# CHES Canada 2023
# ============================================================
cat(" Processing CHES Canada 2023...\n")
ches_ca <- read_csv('~/data/ches/CHES_CA2023.csv', show_col_types = FALSE) %>%
filter(!is.na(partyfacts_id)) %>%
left_join(ches_ca_expert_counts, by = "party_id") %>%
transmute(country = "CA",
year = 2023,
party = partyfacts_id,
project = 'CHES',
n_experts = n_experts,
lrecon_ches = lrecon/10,
galtan_ches = galtan/10) %>%
pivot_longer(cols = lrecon_ches:galtan_ches, names_to = 'var', values_to = 'val') %>%
mutate(n_scale = 10L) %>%
filter(!is.na(val), !is.na(party)) %>%
mutate(type_low = ifelse(var == "lrecon_ches", "pro_welfare", "cosmopolitan"),
type_high = ifelse(var == "lrecon_ches", "pro_market", "traditional"))
ches <- bind_rows(ches, ches_ca)
cat(sprintf(" CHES Canada: %d observations\n", nrow(ches_ca)))
# ============================================================
# CHES Latin America 2020
# ============================================================
cat(" Processing CHES Latin America 2020...\n")
ches_la_link <- read_csv('~/data/ches/ches_la_link.csv', show_col_types = FALSE) %>%
transmute(id = as.character(ches_party_id),
party = partyfacts_id)
ches_la <- read_csv('~/data/ches/ches_la_2020_aggregate_level_v01.csv', show_col_types = FALSE) %>%
left_join(ches_la_expert_counts, by = "party_id") %>%
transmute(country = country_abb,
year = 2020,
id = as.character(party_id),
project = 'CHES',
n_experts = n_experts,
lrecon_ches = lrecon/10,
galtan_ches = galtan/10) %>%
pivot_longer(cols = lrecon_ches:galtan_ches, names_to = 'var', values_to = 'val') %>%
mutate(n_scale = 10L) %>%
left_join(ches_la_link, by = "id") %>%
filter(!is.na(val), !is.na(party), !is.na(country)) %>%
select(-id) %>%
mutate(type_low = ifelse(var == "lrecon_ches", "pro_welfare", "cosmopolitan"),
type_high = ifelse(var == "lrecon_ches", "pro_market", "traditional"))
ches <- bind_rows(ches, ches_la)
cat(sprintf(" CHES Latin America: %d observations\n", nrow(ches_la)))
# ============================================================
# CHES Israel 2021-2022
# ============================================================
cat(" Processing CHES Israel 2021-2022...\n")
ches_il_link <- read_csv('~/data/ches/ches_israel_link.csv', show_col_types = FALSE) %>%
transmute(id = as.character(ches_party_id),
party = partyfacts_id)
ches_il <- read_csv('~/data/ches/CHES_ISRAEL_means_2021_2022.csv', show_col_types = FALSE) %>%
left_join(ches_il_expert_counts, by = c("party_id", "year")) %>%
transmute(country = "IL",
year = year,
id = as.character(party_id),
project = 'CHES',
n_experts = n_experts,
lrecon_ches = lrecon/10,
galtan_ches = galtan/10) %>%
pivot_longer(cols = lrecon_ches:galtan_ches, names_to = 'var', values_to = 'val') %>%
mutate(n_scale = 10L) %>%
left_join(ches_il_link, by = "id") %>%
filter(!is.na(val), !is.na(party), !is.na(country)) %>%
select(-id) %>%
mutate(type_low = ifelse(var == "lrecon_ches", "pro_welfare", "cosmopolitan"),
type_high = ifelse(var == "lrecon_ches", "pro_market", "traditional"))
ches <- bind_rows(ches, ches_il)
cat(sprintf(" CHES Israel: %d observations\n", nrow(ches_il)))
cat(sprintf(" CHES total: %d observations\n", nrow(ches)))
# ============================================================
# CHES General Left-Right (for anchoring)
# ============================================================
cat(" Processing CHES LR anchoring data...\n")
ches_lr <- read_csv('~/data/ches/1999-2019_CHES_dataset_means(v3).csv', show_col_types = FALSE) %>%
rename(country_id = country) %>%
left_join(readRDS('~/data/ches/link.rds'), by = "country_id") %>%
transmute(country = countrycode(country, origin = 'country.name', destination = 'iso2c'),
vote = vote,
year = year,
id = as.character(party_id),
project = 'CHES',
n_experts = as.integer(expert),
val = lrgen/10,
var = 'lr_ches',
n_scale = 10L) %>%
left_join(ches_link, by = c("id", "country")) %>%
filter(!is.na(val), !is.na(party), !is.na(country)) %>%
select(-id)
ches24_lr <- read_csv('~/data/ches/CHES_2024_final_v2.csv', show_col_types = FALSE) %>%
mutate(country_iso2 = convert_country_codes(country)) %>%
left_join(ches24_exp_by_id, by = "party_id") %>%
left_join(ches24_exp_by_name, by = c("country_iso2", "party" = "party_name")) %>%
transmute(country = country_iso2,
vote = vote,
year = 2024,
id = as.character(party_id),
project = 'CHES',
n_experts = coalesce(n_experts_id, n_experts_name),
val = lrgen/10,
var = 'lr_ches',
n_scale = 10L) %>%
left_join(ches_link, by = c("id", "country")) %>%
filter(!is.na(val), !is.na(party), !is.na(country)) %>%
select(-id)
# CHES Canada LR
ches_ca_lr <- read_csv('~/data/ches/CHES_CA2023.csv', show_col_types = FALSE) %>%
filter(!is.na(partyfacts_id)) %>%
left_join(ches_ca_expert_counts, by = "party_id") %>%
transmute(country = "CA",
year = 2023,
party = partyfacts_id,
project = 'CHES',
n_experts = n_experts,
val = lrgen/10,
var = 'lr_ches',
n_scale = 10L) %>%
filter(!is.na(val), !is.na(party))
# CHES Latin America LR
ches_la_lr <- read_csv('~/data/ches/ches_la_2020_aggregate_level_v01.csv', show_col_types = FALSE) %>%
left_join(ches_la_expert_counts, by = "party_id") %>%
transmute(country = country_abb,
year = 2020,
id = as.character(party_id),
project = 'CHES',
n_experts = n_experts,
val = lrgen/10,
var = 'lr_ches',
n_scale = 10L) %>%
left_join(ches_la_link, by = "id") %>%
filter(!is.na(val), !is.na(party), !is.na(country)) %>%
select(-id)
# CHES Israel LR
ches_il_lr <- read_csv('~/data/ches/CHES_ISRAEL_means_2021_2022.csv', show_col_types = FALSE) %>%
left_join(ches_il_expert_counts, by = c("party_id", "year")) %>%
transmute(country = "IL",
year = year,
id = as.character(party_id),
project = 'CHES',
n_experts = n_experts,
val = lrgen/10,
var = 'lr_ches',
n_scale = 10L) %>%
left_join(ches_il_link, by = "id") %>%
filter(!is.na(val), !is.na(party), !is.na(country)) %>%
select(-id)
ches_lr <- bind_rows(ches_lr, ches24_lr, ches_ca_lr, ches_la_lr, ches_il_lr)
# ============================================================
# V-Party Dataset (V5: expanded to 7 variables)
# ============================================================
cat(" Processing V-Party...\n")
vparty_raw <- readRDS('~/data/v-party/V-Dem-CPD-Party-V2.rds')
# Economic 1: v2pariglef_osp (0-6 scale, higher = more right, NO reverse)
vparty_econ1 <- vparty_raw %>%
transmute(
country = countrycode(country_name, origin = "country.name", destination = "iso2c"),
year = year,
party = pf_party_id,
project = "V-Party",
n_experts = as.integer(v2pariglef_nr),
val = v2pariglef_osp / 6,
val_int = as.integer(round(v2pariglef_osp)),
n_scale = 6L,
var = "lrecon_vparty",
type_low = "pro_welfare",
type_high = "pro_market"
) %>%
na.omit()
# Economic 2 (NEW): v2pawelf_osp (0-5 scale, higher = more welfare = LEFT, REVERSE)
vparty_econ2 <- vparty_raw %>%
transmute(
country = countrycode(country_name, origin = "country.name", destination = "iso2c"),
year = year,
party = pf_party_id,
project = "V-Party",
n_experts = as.integer(v2pawelf_nr),
val = 1 - v2pawelf_osp / 5,
val_int = 5L - as.integer(round(v2pawelf_osp)),
n_scale = 5L,
var = "welf_vparty",
type_low = "pro_welfare",
type_high = "pro_market"
) %>%
na.omit()
# Cultural 1 (NEW): v2paimmig_osp (0-4 scale, higher = more pro-immigration = GAL, REVERSE)
vparty_cult1 <- vparty_raw %>%
transmute(
country = countrycode(country_name, origin = "country.name", destination = "iso2c"),
year = year,
party = pf_party_id,
project = "V-Party",
n_experts = as.integer(v2paimmig_nr),
val = 1 - v2paimmig_osp / 4,
val_int = 4L - as.integer(round(v2paimmig_osp)),
n_scale = 4L,
var = "immig_vparty",
type_low = "cosmopolitan",
type_high = "traditional"
) %>%
na.omit()
# Cultural 2 (NEW): v2palgbt_osp (0-4 scale, higher = more pro-LGBT = GAL, REVERSE)
vparty_cult2 <- vparty_raw %>%
transmute(
country = countrycode(country_name, origin = "country.name", destination = "iso2c"),
year = year,
party = pf_party_id,
project = "V-Party",
n_experts = as.integer(v2palgbt_nr),
val = 1 - v2palgbt_osp / 4,
val_int = 4L - as.integer(round(v2palgbt_osp)),
n_scale = 4L,
var = "lgbt_vparty",
type_low = "cosmopolitan",
type_high = "traditional"
) %>%
na.omit()
# Cultural 3 (NEW): v2paculsup_osp (0-4 scale, higher = less cultural superiority = GAL, REVERSE)
vparty_cult3 <- vparty_raw %>%
transmute(
country = countrycode(country_name, origin = "country.name", destination = "iso2c"),
year = year,
party = pf_party_id,
project = "V-Party",
n_experts = as.integer(v2paculsup_nr),
val = 1 - v2paculsup_osp / 4,
val_int = 4L - as.integer(round(v2paculsup_osp)),
n_scale = 4L,
var = "culsup_vparty",
type_low = "cosmopolitan",
type_high = "traditional"
) %>%
na.omit()
# Cultural 4 (NEW): v2parelig_osp (0-4 scale, higher = less religious = GAL, REVERSE)
vparty_cult4 <- vparty_raw %>%
transmute(
country = countrycode(country_name, origin = "country.name", destination = "iso2c"),
year = year,
party = pf_party_id,
project = "V-Party",
n_experts = as.integer(v2parelig_nr),
val = 1 - v2parelig_osp / 4,
val_int = 4L - as.integer(round(v2parelig_osp)),
n_scale = 4L,
var = "relig_vparty",
type_low = "cosmopolitan",
type_high = "traditional"
) %>%
na.omit()
# Cultural 5 (NEW): v2pagender_osp (0-4 scale, higher = more pro-gender equality = GAL, REVERSE)
vparty_cult5 <- vparty_raw %>%
transmute(
country = countrycode(country_name, origin = "country.name", destination = "iso2c"),
year = year,
party = pf_party_id,
project = "V-Party",
n_experts = as.integer(v2pagender_nr),
val = 1 - v2pagender_osp / 4,
val_int = 4L - as.integer(round(v2pagender_osp)),
n_scale = 4L,
var = "gender_vparty",
type_low = "cosmopolitan",
type_high = "traditional"
) %>%
na.omit()
vparty <- bind_rows(vparty_econ1, vparty_econ2,
vparty_cult1, vparty_cult2, vparty_cult3,
vparty_cult4, vparty_cult5)
cat(sprintf(" V-Party: %d observations (7 variables)\n", nrow(vparty)))
cat(sprintf(" lrecon: %d, welf: %d\n", nrow(vparty_econ1), nrow(vparty_econ2)))
cat(sprintf(" immig: %d, lgbt: %d, culsup: %d, relig: %d, gender: %d\n",
nrow(vparty_cult1), nrow(vparty_cult2), nrow(vparty_cult3),
nrow(vparty_cult4), nrow(vparty_cult5)))
# ============================================================
# POPPA Dataset
# ============================================================
cat(" Processing POPPA...\n")
poppa <- readRDS('~/data/POPPA/poppa_integrated_v2.rds') %>%
transmute(country = countrycode(country_short, origin = "iso3c", destination = "iso2c"),
party = partyfacts_id,
val = lrecon/10,
var = "lrecon_poppa",
type_low = "pro_welfare",
type_high = "pro_market",
n_experts = as.integer(n_experts),
n_scale = 10L,
year = as.numeric(sub(".*-\\s*(\\d+)", "\\1", wave)),
project = "POPPA") %>%
na.omit()
cat(sprintf(" POPPA: %d observations\n", nrow(poppa)))
# POPPA General LR
poppa_lr <- readRDS('~/data/POPPA/poppa_integrated_v2.rds') %>%
transmute(country = countrycode(country_short, origin = "iso3c", destination = "iso2c"),
party = partyfacts_id,
val = lroverall/10,
var = "lr_poppa",
n_experts = as.integer(n_experts),
n_scale = 10L,
year = as.numeric(sub(".*-\\s*(\\d+)", "\\1", wave)),
project = "POPPA") %>%
na.omit()
# ============================================================
# GPS (Norris) Survey
# ============================================================
cat(" Processing GPS...\n")
gps <- read.delim("~/data/GPS_norris/Global Party Survey by Party SPSS V2_1_Apr_2020-2.tab") %>%
transmute(n_experts = as.integer(Experts),
lrecon_gps = as.numeric(V4_Scale)/10,
libcon_gps = as.numeric(V6_Scale)/10,
party = ID_PartyFacts,
country = countrycode(ifelse(ISO == "MAC", "MKD", ISO), origin = "iso3c", destination = "iso2c"),
year = 2019,
n_scale = 10L,
project = "GPS") %>%
pivot_longer(cols = lrecon_gps:libcon_gps, names_to = 'var', values_to = 'val') %>%
mutate(type_low = ifelse(var == "lrecon_gps", "pro_welfare", "cosmopolitan"),
type_high = ifelse(var == "lrecon_gps", "pro_market", "traditional")) %>%
na.omit()
cat(sprintf(" GPS: %d observations\n", nrow(gps)))
# ============================================================
# Combine Expert Data
# ============================================================
cat(" Combining expert surveys...\n")
expert_raw <- select(ches, -vote) %>%
bind_rows(vparty) %>%
bind_rows(gps) %>%
bind_rows(poppa) %>%
unique() %>%
arrange(country, party, year, var) %>%
filter(!is.na(val), !is.na(party), !is.na(country), !is.na(var))
# Compute val_int for datasets that don't have it pre-computed
# V-Party already has val_int; CHES/GPS/POPPA need it computed from val * n_scale
expert_raw <- expert_raw %>%
mutate(
val_int = ifelse(is.na(val_int), as.integer(round(val * n_scale)), val_int),
val_int = pmin(pmax(val_int, 0L), n_scale)
)
# Boundary adjustments for continuous val (avoid exact 0 or 1 for Stan prior means)
expert_raw <- expert_raw %>%
mutate(
val = case_when(
val == 0 ~ val + 1e-4,
val == 1 ~ val - 1e-4,
TRUE ~ val
))
# ============================================================
# Combine LR Data
# ============================================================
lr_data_raw <- ches_lr %>%
bind_rows(poppa_lr) %>%
select(-any_of("vote"))
# Boundary adjustments for continuous val (avoid exact 0 or 1)
lr_data_raw <- lr_data_raw %>%
mutate(
val = case_when(
val == 0 ~ val + 1e-4,
val == 1 ~ val - 1e-4,
TRUE ~ val
))
# Compute val_int for LR data
lr_data_raw <- lr_data_raw %>%
mutate(
val_int = as.integer(round(val * n_scale)),
val_int = pmin(pmax(val_int, 0L), n_scale)
)
# ============================================================
# Write Outputs
# ============================================================
write_csv(expert_raw, "expert_raw.csv")
write_csv(lr_data_raw, "lr_data_raw.csv")
cat(sprintf("\nOutputs written:\n"))
cat(sprintf(" expert_raw.csv: %d rows\n", nrow(expert_raw)))
cat(sprintf(" lr_data_raw.csv: %d rows\n", nrow(lr_data_raw)))
cat("\n Expert data by source:\n")
expert_raw %>%
group_by(project) %>%
summarise(n = n(), .groups = "drop") %>%
print()
cat("\n New columns check:\n")
cat(sprintf(" val_int range: %d - %d\n", min(expert_raw$val_int), max(expert_raw$val_int)))
cat(sprintf(" n_scale values: %s\n", paste(sort(unique(expert_raw$n_scale)), collapse = ", ")))
cat(sprintf(" n_experts non-NA: %d / %d\n", sum(!is.na(expert_raw$n_experts)), nrow(expert_raw)))
+388
View File
@@ -0,0 +1,388 @@
# 00e_process_morgan.R
# Process Morgan (1976) expert party position data
#
# Source: Morgan, Michael-John (1976). "The Modelling of Governmental
# Coalition Formation: A Policy-Based Approach with Interval Measurement."
# PhD dissertation, University of Michigan.
#
# Data extracted from Appendix B.3 (Tables B.3.1-B.3.12) via OCR.
# Position scores are 25%-truncated means (midmeans) from expert surveys.
# Scale: 0-100 (left-right)
library(tidyverse)
cat("Processing Morgan (1976) expert party position data...\n")
# Load raw extracted data
morgan_raw <- read_csv("morgan_positions_raw.csv", show_col_types = FALSE)
cat(sprintf("Loaded %d party-period observations from %d countries\n",
nrow(morgan_raw), n_distinct(morgan_raw$country)))
# Load PartyFacts linkage data
partyfacts <- read_csv("partyfacts-external-parties.csv", show_col_types = FALSE)
# Filter to Morgan dataset entries
morgan_pf <- partyfacts %>%
filter(dataset_key == "morgan") %>%
select(country, name_short, name_english, year_first, year_last,
external_id, partyfacts_id) %>%
rename(party_abbrev_pf = name_short)
cat(sprintf("Found %d Morgan parties in PartyFacts\n", nrow(morgan_pf)))
# Map extracted abbreviations to PartyFacts abbreviations
# Some adjustments needed due to OCR/transcription differences
abbrev_map <- tribble(
~country, ~party_abbrev, ~party_abbrev_pf,
# Denmark
"DNK", "SOCd", "SOCD",
"DNK", "SOCL", "SOCL",
"DNK", "COMM", "COMM",
"DNK", "RAD", "RAD",
"DNK", "LIB", "LIB",
"DNK", "CONS", "CONS",
"DNK", "LS", "LS",
"DNK", "LC", "LC",
"DNK", "JUST", "JUST",
# Finland
"FIN", "SKDL", "SKDL",
"FIN", "SOCd", "SOCD",
"FIN", "PROG", "PROG",
"FIN", "AGR", "AGR",
"FIN", "SWPP", "SWPP",
"FIN", "CONS", "CONS",
"FIN", "NPF", "NPF",
"FIN", "PDEM", "PDEM",
"FIN", "SDWS", "SDWS",
"FIN", "CENT", "CENT",
"FIN", "FRP", "FRP",
"FIN", "LIB", "LIB",
# Iceland
"ISL", "COMM", "COMM",
"ISL", "SOCd", "SOCD",
"ISL", "PROG", "PROG",
"ISL", "LIB", "LIB",
"ISL", "INDP", "INDP",
"ISL", "CONS", "CONS",
"ISL", "LLIB", "LLIB",
# Norway
"NOR", "LAB", "LAB",
"NOR", "LIB", "LIB",
"NOR", "AGR", "AGR",
"NOR", "CONS", "CONS",
"NOR", "COMM", "COMM",
"NOR", "SOCL", "SOCL",
"NOR", "CHPP", "CHPP",
"NOR", "CENT", "CENT",
# Sweden
"SWE", "COMM", "COMM",
"SWE", "SOCd", "SOCD",
"SWE", "AGR", "AGR",
"SWE", "LIB", "LIB",
"SWE", "CONS", "CONS",
"SWE", "CENT", "CENT",
# Netherlands
"NLD", "CPN", "CPN",
"NLD", "SOCd", "SOCD",
"NLD", "RAD", "RAD",
"NLD", "KVP", "KVP",
"NLD", "CHU", "CHU",
"NLD", "LIB", "LIB",
"NLD", "ARP", "ARP",
"NLD", "SGP", "SGP",
"NLD", "NSB", "NSB",
"NLD", "PVDA", "PVDA",
"NLD", "VVD", "VVD",
"NLD", "PSP", "PSP",
"NLD", "PPR", "PPR",
"NLD", "D66", "D66",
"NLD", "DS70", "DS70",
"NLD", "GPV", "GPV",
"NLD", "BP", "BP",
# Belgium
"BEL", "COMM", "COMM",
"BEL", "POB", "POB",
"BEL", "CATH", "CATH",
"BEL", "LIB", "LIB",
"BEL", "FNAT", "FNAT",
"BEL", "REX", "REX",
"BEL", "PSB", "PSB",
"BEL", "RW", "RW",
"BEL", "PSC", "PSC",
"BEL", "FDF", "FDF",
"BEL", "VOLK", "VOLK",
"BEL", "PLP", "PLP",
# France (Fourth Republic)
"FRA", "PCF", "PCF",
"FRA", "SFIO", "SFIO",
"FRA", "MRP", "MRP",
"FRA", "RDA", "RDA",
"FRA", "UDSR", "UDSR",
"FRA", "RAD", "RAD",
"FRA", "RS", "RS",
"FRA", "RPF", "RPF",
"FRA", "AR", "AR",
"FRA", "ARS", "ARS",
"FRA", "RI", "RI",
"FRA", "CNIP", "CNIP",
"FRA", "PUS", "PUS",
"FRA", "PAYS", "PAYS",
"FRA", "AP", "AP",
"FRA", "PRL", "PRL",
"FRA", "POUJ", "POUJ",
# Weimar Germany
"DEU", "KPD", "KPD",
"DEU", "SDAP", "SDAP",
"DEU", "DDP", "DDP",
"DEU", "DZP", "DZP",
"DEU", "BVP", "BVP",
"DEU", "DVP", "DVP",
"DEU", "RDMW", "RDMW",
"DEU", "LVP", "LVP",
"DEU", "DNVP", "DNVP",
"DEU", "NAZI", "NAZI",
# Italy
"ITA", "PCI", "PCI",
"ITA", "PSIU", "PSIU",
"ITA", "PSI", "PSI",
"ITA", "PSDI", "PSDI",
"ITA", "PRI", "PRI",
"ITA", "DC", "DC",
"ITA", "PLI", "PLI",
"ITA", "MON", "MON",
"ITA", "MSI", "MSI",
# Luxembourg
"LUX", "COMM", "COMM",
"LUX", "SOCd", "SOCD",
"LUX", "CSOC", "CSOC",
"LUX", "GRPD", "GRPD",
# Israel
"ISR", "RAKA", "RAKA",
"ISR", "MAKI", "MAKI",
"ISR", "MAPM", "MAPM",
"ISR", "MADT", "MADT",
"ISR", "ADUT", "ADUT",
"ISR", "MAAR", "MAAR",
"ISR", "LAB", "LAB",
"ISR", "MAPI", "MAPI",
"ISR", "PAUG", "PAUG",
"ISR", "RAFI", "RAFI",
"ISR", "PROG", "PROG",
"ISR", "ILIB", "ILIB",
"ISR", "NRP", "NRP",
"ISR", "URF", "URF",
"ISR", "LIB", "LIB",
"ISR", "NATL", "NATL",
"ISR", "TORA", "TORA",
"ISR", "LIKD", "LIKD",
"ISR", "ZION", "ZION",
"ISR", "GHAL", "GHAL",
"ISR", "AGDT", "AGDT",
"ISR", "HRUT", "HRUT"
)
# Some parties in raw data that don't have exact matches - need special handling
# (e.g., parties that only exist in one period in PartyFacts but appear in both)
# We'll join using the period-based matching
# Expand periods to years for matching
morgan_expanded <- morgan_raw %>%
mutate(
year_start = as.integer(str_extract(period, "^\\d{4}")),
year_end = as.integer(str_extract(period, "\\d{4}$"))
)
# Join with abbreviation map
morgan_mapped <- morgan_expanded %>%
left_join(abbrev_map, by = c("country", "party_abbrev"))
# Check for unmatched abbreviations
unmatched_abbrev <- morgan_mapped %>%
filter(is.na(party_abbrev_pf)) %>%
distinct(country, party_abbrev)
if (nrow(unmatched_abbrev) > 0) {
cat("\nWarning: Unmatched abbreviations:\n")
print(unmatched_abbrev)
}
# Join with PartyFacts
morgan_joined <- morgan_mapped %>%
left_join(morgan_pf, by = c("country", "party_abbrev_pf")) %>%
# For parties with overlapping periods, use period overlap
mutate(
period_overlap = pmax(0,
pmin(year_end, year_last) - pmax(year_start, year_first) + 1)
) %>%
# Keep best match per party-period (max overlap)
group_by(country, party_abbrev, period) %>%
slice_max(period_overlap, n = 1, with_ties = FALSE) %>%
ungroup()
# Check for unmatched parties
unmatched <- morgan_joined %>%
filter(is.na(partyfacts_id)) %>%
distinct(country, party_abbrev, party_name, period)
if (nrow(unmatched) > 0) {
cat(sprintf("\n%d party-periods without PartyFacts match:\n", nrow(unmatched)))
print(unmatched)
}
# Dedup: when multiple abbreviations map to the same PF ID, keep only one
matched <- morgan_joined %>%
filter(!is.na(partyfacts_id)) %>%
group_by(country, partyfacts_id, period) %>%
slice(1) %>%
ungroup()
cat(sprintf("\nMatched %d of %d party-period observations (%.1f%%)\n",
nrow(matched), nrow(morgan_raw),
100 * nrow(matched) / nrow(morgan_raw)))
# Normalize position to [0,1] scale
# Original scale: 0-100
# Apply boundary adjustments like other expert data
eps <- 0.005
morgan_processed <- matched %>%
mutate(
# Normalize to [0,1]
lr_morgan = position / 100,
# Apply boundary adjustments
lr_morgan = case_when(
lr_morgan <= 0 ~ eps,
lr_morgan >= 1 ~ 1 - eps,
TRUE ~ lr_morgan
),
# Calculate standard error (sd / sqrt(n))
lr_morgan_se = (sd / 100) / sqrt(n_surveys),
# Set minimum SE for extreme parties (sd=0)
lr_morgan_se = pmax(lr_morgan_se, 0.01)
) %>%
select(
country,
partyfacts_id,
period,
year_start,
year_end,
party_abbrev,
party_name,
lr_morgan,
lr_morgan_se,
n_surveys
) %>%
arrange(country, year_start, lr_morgan)
# Summary statistics
cat("\nSummary of processed Morgan data:\n")
cat(sprintf(" Countries: %d\n", n_distinct(morgan_processed$country)))
cat(sprintf(" Parties: %d\n", n_distinct(morgan_processed$partyfacts_id)))
cat(sprintf(" Observations: %d\n", nrow(morgan_processed)))
# Distribution of positions
cat("\nPosition distribution:\n")
print(summary(morgan_processed$lr_morgan))
# Write output
write_csv(morgan_processed, "morgan_data.csv")
cat(sprintf("\nWrote morgan_data.csv with %d rows\n", nrow(morgan_processed)))
# Also provide a summary by country and period
summary_by_country <- morgan_processed %>%
group_by(country, period) %>%
summarise(
n_parties = n(),
mean_pos = mean(lr_morgan),
sd_pos = sd(lr_morgan),
.groups = "drop"
)
cat("\nSummary by country and period:\n")
print(summary_by_country, n = 50)
# ============================================================
# Generate lr_data-compatible output for pipeline integration
# ============================================================
cat("\n============================================================\n")
cat("Generating lr_data-compatible output (postwar only)\n")
cat("============================================================\n")
# Load text_data to get party-years with manifesto/PolDem coverage
text_data <- read_csv("text_data.csv", show_col_types = FALSE)
# Convert Morgan ISO3 country codes to ISO2 (matching text_data format)
iso3_to_iso2 <- c(
"DNK" = "DK", "FIN" = "FI", "ISL" = "IS", "NOR" = "NO", "SWE" = "SE",
"NLD" = "NL", "BEL" = "BE", "DEU" = "DE", "FRA" = "FR", "ITA" = "IT",
"LUX" = "LU", "ISR" = "IL"
)
# Filter to postwar periods only (1945+)
morgan_postwar <- morgan_processed %>%
filter(year_end >= 1945) %>%
mutate(country_iso2 = iso3_to_iso2[country])
cat(sprintf("Postwar Morgan observations: %d party-periods\n", nrow(morgan_postwar)))
cat(sprintf("Countries: %s\n", paste(unique(morgan_postwar$country_iso2), collapse = ", ")))
# Get unique party-years from text_data
text_party_years <- text_data %>%
select(party, country, year) %>%
distinct()
cat(sprintf("Unique party-years in text_data: %d\n", nrow(text_party_years)))
# For each Morgan party-period, expand to all years where that party has text data
# within the Morgan period range (1945-1973 for postwar)
morgan_lr <- morgan_postwar %>%
# Join with text_data party-years
# Many-to-many is expected: one Morgan party-period maps to multiple years
inner_join(
text_party_years,
by = c("partyfacts_id" = "party", "country_iso2" = "country"),
relationship = "many-to-many"
) %>%
# Keep only years within the Morgan period
filter(year >= year_start & year <= year_end) %>%
# Format for lr_data.csv compatibility
transmute(
country = country_iso2,
party = partyfacts_id,
var = "lr_morgan",
year = year,
val = lr_morgan,
project = "Morgan",
# Morgan's continuous 0-100 scale is discretized to 10 points (matching CHES
# resolution) with the actual number of experts. The reconstructed sum
# round(mean × K × 10) is analogous to how CHES means are handled.
# See docs/k_scaling_validation.md Section 4.
n_scale = 10L,
val_int = as.integer(round(lr_morgan * 10)),
n_experts = as.integer(n_surveys)
) %>%
distinct() %>%
arrange(country, party, year)
cat(sprintf("\nGenerated %d lr_morgan observations\n", nrow(morgan_lr)))
cat(sprintf(" Unique parties: %d\n", n_distinct(morgan_lr$party)))
cat(sprintf(" Year range: %d-%d\n", min(morgan_lr$year), max(morgan_lr$year)))
# Summary by country
morgan_lr_summary <- morgan_lr %>%
group_by(country) %>%
summarise(
n_parties = n_distinct(party),
n_obs = n(),
year_min = min(year),
year_max = max(year),
.groups = "drop"
)
cat("\nMorgan L-R data by country:\n")
print(morgan_lr_summary, n = 20)
# Write morgan_lr.csv
write_csv(morgan_lr, "morgan_lr.csv")
cat(sprintf("\nWrote morgan_lr.csv with %d rows\n", nrow(morgan_lr)))
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)"
project_root="$(cd "$repo_root/.." && pwd -P)"
raw_data_dir="${PARTY2D_RAW_DATA_DIR:-$project_root/_local/raw}"
required_files=(
"poldem/poldem-election_all.csv"
)
optional_files=(
"manifesto/MPDataset_MPDS2025a.csv"
)
echo "Raw data directory: $raw_data_dir"
echo
echo "Required raw inputs for regeneration:"
missing=0
for rel in "${required_files[@]}"; do
path="$raw_data_dir/$rel"
if [ -s "$path" ]; then
bytes="$(wc -c < "$path")"
read -r sha _ < <(sha256sum "$path")
echo " OK $rel ($bytes bytes, sha256=$sha)"
else
echo " MISSING $rel"
missing=1
fi
done
echo
echo "Optional raw inputs used only if cached processed files are regenerated:"
for rel in "${optional_files[@]}"; do
path="$raw_data_dir/$rel"
if [ -s "$path" ]; then
bytes="$(wc -c < "$path")"
read -r sha _ < <(sha256sum "$path")
echo " OK $rel ($bytes bytes, sha256=$sha)"
else
echo " MISSING $rel"
fi
done
if [ "$missing" -ne 0 ]; then
echo
echo "At least one required raw input is missing." >&2
echo "See docs/RAW_DATA_SOURCES.md for download/local-placement instructions." >&2
exit 1
fi
echo
echo "Required raw data preflight passed."
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)"
logs_dir="$repo_root/outputs/logs"
model_dir="$repo_root/outputs/model_outputs/latest"
latest_log="$(ls "$logs_dir"/full_run_*.log 2>/dev/null | sort | tail -n 1 || true)"
latest_run="$(ls -d "$model_dir"/run_* 2>/dev/null | sort | tail -n 1 || true)"
echo "party2d full-run progress"
echo "repo: $repo_root"
echo
if [[ -n "$latest_log" ]]; then
echo "Latest durable log: $latest_log"
echo "--- last 40 log lines ---"
tail -n 40 "$latest_log"
else
echo "No durable full_run_*.log found under $logs_dir"
fi
echo
if [[ -n "$latest_run" ]]; then
echo "Latest model run: $latest_run"
metrics="$latest_run/diagnostics/run_metrics.json"
if [[ -f "$metrics" ]]; then
echo "Metrics: $metrics"
echo "--- key metrics ---"
py="$(command -v python3 || command -v python || true)"
if [[ -z "$py" && -x /run/current-system/sw/bin/python3 ]]; then
py=/run/current-system/sw/bin/python3
fi
if [[ -z "$py" ]]; then
echo "No Python interpreter found for JSON summary; metrics file is available at: $metrics"
exit 0
fi
"$py" - "$metrics" <<'PY'
import json, sys
path = sys.argv[1]
with open(path) as f:
m = json.load(f)
print("status:", m.get("status"))
cfg = m.get("config", {})
print("chains:", cfg.get("num_chains"), "warmup:", cfg.get("num_warmup"), "samples:", cfg.get("num_samples"), "max_depth:", cfg.get("max_depth"), "refresh:", cfg.get("refresh"))
timing = m.get("timing", {})
if timing:
print("walltime_minutes:", timing.get("walltime_minutes"))
print("reported_warmup_seconds:", timing.get("reported_warmup_seconds"))
print("reported_sampling_seconds:", timing.get("reported_sampling_seconds"))
agg = m.get("aggregate", {})
if agg:
print("divergences:", agg.get("divergences"), "treedepth_hits:", agg.get("treedepth_hits"), "mean_leapfrog:", agg.get("mean_leapfrog"))
print("cmdstan_config_verified:", m.get("cmdstan_config_verified"))
PY
else
echo "No archived metrics yet. If the run is still sampling, watch the durable log with:"
if [[ -n "$latest_log" ]]; then
echo " tail -f \"$latest_log\""
else
echo " tail -f outputs/logs/full_run_<timestamp>.log"
fi
fi
else
echo "No completed model run found under $model_dir"
fi
echo
echo "To follow a running job live:"
if [[ -n "$latest_log" ]]; then
echo " tail -f \"$latest_log\""
else
echo " tail -f outputs/logs/full_run_<timestamp>.log"
fi