dclab 0.61.2__cp312-cp312-macosx_10_9_x86_64.whl → 2.18.0__cp312-cp312-macosx_10_9_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dclab might be problematic. Click here for more details.

dclab/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.61.2'
16
- __version_tuple__ = version_tuple = (0, 61, 2)
15
+ __version__ = version = '2.18.0'
16
+ __version_tuple__ = version_tuple = (2, 18, 0)
Binary file
dclab/lme4/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """A wrapper around R with the lme4 package"""
2
- from . import rsetup, wrapr # noqa: F401
3
- from .wrapr import Rlme4, bootstrapped_median_distributions # noqa: F401
4
- from .rsetup import ( # noqa: F401
5
- get_r_path, get_r_version, require_lme4, set_r_path)
2
+ # flake8: noqa: F401
3
+ from . import rlibs, rsetup, wrapr
4
+ from .wrapr import Rlme4, bootstrapped_median_distributions
5
+ from .rsetup import get_r_path, get_r_version, install_lme4, set_r_path
dclab/lme4/rlibs.py ADDED
@@ -0,0 +1,93 @@
1
+ import importlib
2
+ import os
3
+ import warnings
4
+
5
+ from ..external.packaging import parse as parse_version
6
+
7
+ #: Minimum R version
8
+ #: This is actually a dependency for rpy2, because the API changed then
9
+ #: (ffi.error: symbol 'R_tryCatchError' not found in library).
10
+ R_MIN_VERSION = "3.6.0"
11
+
12
+ #: Minimum rpy2 version
13
+ RPY2_MIN_VERSION = "2.9.4"
14
+
15
+ R_SUBMODULES = [
16
+ "rpy2.robjects.packages",
17
+ "rpy2.situation",
18
+ "rpy2.robjects.vectors",
19
+ ]
20
+
21
+ R_SUBMODULES_3 = [
22
+ "rpy2.rinterface_lib.callbacks",
23
+ ]
24
+
25
+
26
+ class RPY2UnavailableError(BaseException):
27
+ pass
28
+
29
+
30
+ class RPY2ImportError(RPY2UnavailableError):
31
+ pass
32
+
33
+
34
+ class RPY2OutdatedError(RPY2UnavailableError):
35
+ pass
36
+
37
+
38
+ class RUnavailableError(BaseException):
39
+ pass
40
+
41
+
42
+ class ROutdatedError(RUnavailableError):
43
+ pass
44
+
45
+
46
+ class MockRPackage:
47
+ def __init__(self, exception):
48
+ self.exception = exception
49
+
50
+ def __getattr__(self, item):
51
+ raise self.exception
52
+
53
+
54
+ def import_r_submodules():
55
+ importlib.import_module("rpy2.situation")
56
+ r_home = rpy2.situation.get_r_home()
57
+ if r_home is not None:
58
+ if os.environ.get("R_HOME", None) is None:
59
+ # set R_HOME globally (https://github.com/rpy2/rpy2/issues/796)
60
+ os.environ["R_HOME"] = r_home
61
+ if rpy2_is_version_3:
62
+ mods = R_SUBMODULES + R_SUBMODULES_3
63
+ else:
64
+ mods = R_SUBMODULES
65
+ try:
66
+ for sm in mods:
67
+ importlib.import_module(sm)
68
+ except rpy2.rinterface_lib.openrlib.ffi.error as exc:
69
+ # This error happens when the installed R version is too old:
70
+ # "ffi.error: symbol 'R_tryCatchError' not found in library"
71
+ raise ROutdatedError(
72
+ f"Encountered '{exc.__class__.__name__}: {exc}'. "
73
+ f"Please make sure you have 'R>={R_MIN_VERSION}'!")
74
+
75
+
76
+ try:
77
+ rpy2 = importlib.import_module("rpy2")
78
+ if parse_version(rpy2.__version__) < parse_version(RPY2_MIN_VERSION):
79
+ raise RPY2OutdatedError(f"Please install 'rpy2>={RPY2_MIN_VERSION}'!")
80
+ except ImportError:
81
+ rpy2 = MockRPackage(
82
+ RPY2ImportError(f"Please install 'rpy2>={RPY2_MIN_VERSION}'!"))
83
+ rpy2_is_version_3 = False
84
+ except BaseException as e:
85
+ rpy2 = MockRPackage(e)
86
+ rpy2_is_version_3 = False
87
+ else:
88
+ rpy2_is_version_3 = parse_version(rpy2.__version__) >= parse_version("3.0")
89
+ try:
90
+ import_r_submodules()
91
+ except RUnavailableError as e:
92
+ warnings.warn("There is an issue with the linked R version: "
93
+ + f"{e.__class__.__name__}: {e}")
dclab/lme4/rsetup.py CHANGED
@@ -1,142 +1,185 @@
1
1
  import logging
2
2
  import os
3
- import pathlib
4
3
  import subprocess as sp
5
- import sys
6
4
 
7
- logger = logging.getLogger(__name__)
5
+ from .rlibs import (
6
+ RUnavailableError, rpy2, rpy2_is_version_3, import_r_submodules)
8
7
 
9
- _has_lme4 = None
10
- _has_r = None
8
+ # Disable rpy2 logger because of unnecessary prints to stdout
9
+ logging.getLogger("rpy2.rinterface_lib.callbacks").disabled = True
11
10
 
12
11
 
13
12
  class RNotFoundError(BaseException):
14
13
  pass
15
14
 
16
15
 
17
- def get_r_path():
18
- """Return the path of the R executable"""
19
- # Maybe the user set the executable already?
20
- r_exec = os.environ.get("R_EXEC")
21
- if r_exec is not None:
22
- r_exec = pathlib.Path(r_exec)
23
- if r_exec.is_file():
24
- return r_exec
25
-
26
- # Try to determine the path to the executable from R_HOME
27
- r_home = os.environ.get("R_HOME")
28
- if not pathlib.Path(r_home).is_dir():
29
- logger.warning(f"R_HOME Directory does not exist: {r_home}")
30
- r_home = None
31
- if r_home is None:
32
- cmd = ("R", "RHOME")
33
- try:
34
- tmp = sp.check_output(cmd, universal_newlines=True)
35
- # may raise FileNotFoundError, WindowsError, etc
36
- r_home = tmp.split(os.linesep)
37
- except BaseException:
38
- pass
16
+ class AutoRConsole(object):
17
+ """Helper class for catching R console output"""
18
+ lock = False
19
+ perform_lock = rpy2_is_version_3
20
+
21
+ def __init__(self):
22
+ """
23
+ By default, this console always returns "yes" when asked a
24
+ question. If you need something different, you can subclass
25
+ and override `consoleread` fucntion. The console stream is
26
+ recorded in `self.stream`.
27
+ """
28
+ self.stream = [["init", "Starting RConsole class\n"]]
29
+ if AutoRConsole.perform_lock:
30
+ if AutoRConsole.lock:
31
+ raise ValueError("Only one RConsole instance allowed!")
32
+ AutoRConsole.lock = True
33
+ self.original_funcs = {
34
+ "consoleread": rpy2.rinterface_lib.callbacks.consoleread,
35
+ "consolewrite_print":
36
+ rpy2.rinterface_lib.callbacks.consolewrite_print,
37
+ "consolewrite_warnerror":
38
+ rpy2.rinterface_lib.callbacks.consolewrite_warnerror,
39
+ }
40
+ rpy2.rinterface_lib.callbacks.consoleread = self.consoleread
41
+ rpy2.rinterface_lib.callbacks.consolewrite_print = \
42
+ self.consolewrite_print
43
+ rpy2.rinterface_lib.callbacks.showmessage = \
44
+ self.consolewrite_print
45
+
46
+ rpy2.rinterface_lib.callbacks.consolewrite_warnerror = \
47
+ self.consolewrite_warnerror
48
+ # Set locale (to get always English messages)
49
+ rpy2.robjects.r('Sys.setlocale("LC_MESSAGES", "C")')
50
+ rpy2.robjects.r('Sys.setlocale("LC_CTYPE", "C")')
51
+
52
+ def __enter__(self):
53
+ return self
54
+
55
+ def __exit__(self, *args):
56
+ if AutoRConsole.perform_lock:
57
+ AutoRConsole.lock = False
58
+ rpy2.rinterface_lib.callbacks.consoleread = \
59
+ self.original_funcs["consoleread"]
60
+ rpy2.rinterface_lib.callbacks.consolewrite_print = \
61
+ self.original_funcs["consolewrite_print"]
62
+ rpy2.rinterface_lib.callbacks.consolewrite_warnerror = \
63
+ self.original_funcs["consolewrite_warnerror"]
64
+
65
+ def close(self):
66
+ """Remove the rpy2 monkeypatches"""
67
+ self.__exit__()
68
+
69
+ def consoleread(self, prompt):
70
+ """Read user input, returns "yes" by default"""
71
+ self.write_to_stream("consoleread", prompt + "YES")
72
+ return "yes"
73
+
74
+ def consolewrite_print(self, s):
75
+ self.write_to_stream("consolewrite_print", s)
76
+
77
+ def consolewrite_warnerror(self, s):
78
+ self.write_to_stream("consolewrite_warnerror", s)
79
+
80
+ def write_to_stream(self, topic, s):
81
+ prev_topic = self.stream[-1][0]
82
+ same_topic = prev_topic == topic
83
+ unfinished_line = self.stream[-1][1][-1] not in ["\n", "\r"]
84
+ if same_topic and unfinished_line:
85
+ # append to previous line
86
+ self.stream[-1][1] += s
39
87
  else:
40
- if r_home[0].startswith("WARNING"):
41
- r_home = r_home[1].strip()
42
- else:
43
- r_home = r_home[0].strip()
44
- if r_home is None:
45
- raise RNotFoundError(
46
- "Cannot find R, please set the `R_HOME` environment variable "
47
- "or use `set_r_path`.")
48
-
49
- r_home = pathlib.Path(r_home)
50
-
51
- if sys.platform == "win32" and "64 bit" in sys.version:
52
- r_exec = r_home / "bin" / "x64" / "R.exe"
53
- else:
54
- r_exec = r_home / "bin" / "R"
55
- if not r_exec.is_file():
56
- raise RNotFoundError(
57
- f"Expected R binary at '{r_exec}' does not exist!")
58
- logger.info(f"R path: {r_exec}")
59
- return r_exec
88
+ self.stream.append([topic, s])
89
+
90
+ def get_prints(self):
91
+ prints = []
92
+ for line in self.stream:
93
+ if line[0] == "consolewrite_print":
94
+ prints.append(line[1].strip())
95
+ return prints
96
+
97
+ def get_warnerrors(self):
98
+ warnerrors = []
99
+ for line in self.stream:
100
+ if line[0] == "consolewrite_warnerror":
101
+ warnerrors.append(line[1].strip())
102
+ return warnerrors
60
103
 
61
104
 
62
- def get_r_script_path():
63
- """Return the path to the Rscript executable"""
64
- return get_r_path().with_name("Rscript")
105
+ def check_r():
106
+ """Make sure R is installed an R HOME is set"""
107
+ if not has_r():
108
+ raise RNotFoundError("Cannot find R, please set its path with the "
109
+ + "`set_r_path` function.")
110
+
111
+
112
+ def get_r_path():
113
+ """Get the path of the R executable/binary from rpy2"""
114
+ r_home = rpy2.situation.get_r_home()
115
+ return rpy2.situation.get_r_exec(r_home)
65
116
 
66
117
 
67
118
  def get_r_version():
68
- """Return the full R version string"""
69
- require_r()
70
- cmd = ("R", "--version")
71
- logger.debug(f"Looking for R version with: {cmd}")
72
- tmp = sp.check_output(cmd, stderr=sp.STDOUT)
73
- r_version = tmp.decode("ascii", "ignore").split(os.linesep)
74
- if r_version[0].startswith("WARNING"):
75
- r_version = r_version[1]
76
- else:
77
- r_version = r_version[0]
78
- logger.info(f"R version found: {r_version}")
79
- # get the actual version string
80
- if r_version.startswith("R version "):
81
- r_version = r_version.split(" ", 2)[2]
82
- return r_version.strip()
119
+ check_r()
120
+ ver_string = rpy2.situation.r_version_from_subprocess().strip()
121
+ if ver_string:
122
+ # get the actual version string
123
+ if ver_string.startswith("R version "):
124
+ ver_string = ver_string.split(" ")[2]
125
+ return ver_string
83
126
 
84
127
 
85
128
  def has_lme4():
86
129
  """Return True if the lme4 package is installed"""
87
- global _has_lme4
88
- if _has_lme4:
89
- return True
90
- require_r()
91
- for pkg in ["lme4", "statmod", "nloptr"]:
92
- res = run_command(("R", "-q", "-e", f"system.file(package='{pkg}')"))
93
- if not res.split("[1]")[1].count(pkg):
94
- avail = False
95
- break
96
- else:
97
- avail = _has_lme4 = True
98
- return avail
130
+ check_r()
131
+ lme4_there = rpy2.robjects.packages.isinstalled("lme4")
132
+ statmod_there = rpy2.robjects.packages.isinstalled("statmod")
133
+ nloptr_there = rpy2.robjects.packages.isinstalled("nloptr")
134
+ return lme4_there and statmod_there and nloptr_there
99
135
 
100
136
 
101
137
  def has_r():
102
138
  """Return True if R is available"""
103
- global _has_r
104
- if _has_r:
105
- return True
106
139
  try:
107
- hasr = bool(get_r_path())
108
- except RNotFoundError:
140
+ hasr = rpy2.situation.get_r_home() is not None
141
+ except RUnavailableError:
109
142
  hasr = False
110
- if hasr:
111
- _has_r = True
112
143
  return hasr
113
144
 
114
145
 
115
- def require_lme4():
146
+ def import_lme4():
147
+ check_r()
148
+ if has_lme4():
149
+ lme4pkg = rpy2.robjects.packages.importr("lme4")
150
+ else:
151
+ raise ValueError(
152
+ "The R package 'lme4' is not installed, please install it via "
153
+ + "`dclab.lme4.rsetup.install_lme4()` or by executing "
154
+ + "in a shell: R -e " + '"install.packages(' + "'lme4', "
155
+ + "repos='http://cran.rstudio.org')" + '"')
156
+ return lme4pkg
157
+
158
+
159
+ def install_lme4():
116
160
  """Install the lme4 package (if not already installed)
117
161
 
118
- Besides ``lme4``, this also installs ``nloptr`` and ``statmod``.
119
162
  The packages are installed to the user data directory
120
- given in :const:`lib_path` from the http://cran.rstudio.org mirror.
163
+ given in :const:`lib_path`.
121
164
  """
122
- require_r()
165
+ check_r()
123
166
  if not has_lme4():
124
- run_command((
125
- "R", "-e", "install.packages(c('statmod','nloptr','lme4'),"
126
- "repos='http://cran.rstudio.org')"))
127
-
128
-
129
- def require_r():
130
- """Make sure R is installed an R HOME is set"""
131
- if not has_r():
132
- raise RNotFoundError("Cannot find R, please set its path with the "
133
- "`set_r_path` function or set the `RHOME` "
134
- "environment variable.")
167
+ # import R's utility package
168
+ utils = rpy2.robjects.packages.importr('utils')
169
+ # select the first mirror in the list
170
+ utils.chooseCRANmirror(ind=1)
171
+ # install lme4 to user data directory (say yes to user dir install)
172
+ with AutoRConsole() as rc:
173
+ # install statmod and nloptr first
174
+ # (Doesn't R have package dependencies?!)
175
+ utils.install_packages(
176
+ rpy2.robjects.vectors.StrVector(["statmod", "nloptr", "lme4"]))
177
+ return rc
135
178
 
136
179
 
137
- def run_command(cmd):
138
- """Run a command via subprocess"""
139
- if hasattr(sp, "STARTUPINFO"):
180
+ def set_r_path(r_path):
181
+ """Set the path of the R executable/binary for rpy2"""
182
+ if hasattr(sp, 'STARTUPINFO'):
140
183
  # On Windows, subprocess calls will pop up a command window by
141
184
  # default when run from Pyinstaller with the ``--noconsole``
142
185
  # option. Avoid this distraction.
@@ -149,26 +192,16 @@ def run_command(cmd):
149
192
  si = None
150
193
  env = None
151
194
 
152
- # Convert paths to strings
153
- cmd = [str(cc) for cc in cmd]
154
-
155
- tmp = sp.check_output(cmd,
195
+ tmp = sp.check_output((r_path, 'RHOME'),
156
196
  startupinfo=si,
157
197
  env=env,
158
- stderr=sp.STDOUT,
159
198
  text=True,
160
199
  )
161
- return tmp
162
-
163
-
164
- def set_r_path(r_path):
165
- """Set the path of the R executable/binary"""
166
- tmp = run_command((r_path, "RHOME"))
167
200
 
168
201
  r_home = tmp.split(os.linesep)
169
- if r_home[0].startswith("WARNING"):
202
+ if r_home[0].startswith('WARNING'):
170
203
  res = r_home[1]
171
204
  else:
172
205
  res = r_home[0].strip()
173
206
  os.environ["R_HOME"] = res
174
- os.environ["R_EXEC"] = str(pathlib.Path(r_path).resolve())
207
+ import_r_submodules()
dclab/lme4/wrapr.py CHANGED
@@ -1,19 +1,18 @@
1
1
  """R lme4 wrapper"""
2
- import logging
3
2
  import numbers
4
- import pathlib
5
- import tempfile
3
+ import warnings
6
4
 
7
- import importlib_resources
8
5
  import numpy as np
9
6
 
10
7
  from .. import definitions as dfn
11
8
  from ..rtdc_dataset.core import RTDCBase
12
9
 
10
+ from .rlibs import rpy2
13
11
  from . import rsetup
14
12
 
15
13
 
16
- logger = logging.getLogger(__name__)
14
+ class Lme4InstallWarning(UserWarning):
15
+ pass
17
16
 
18
17
 
19
18
  class Rlme4(object):
@@ -39,12 +38,19 @@ class Rlme4(object):
39
38
  #: list of [RTDCBase, column, repetition, chip_region]
40
39
  self.data = []
41
40
 
41
+ #: model function
42
+ self.r_func_model = "feature ~ group + (1 + group | repetition)"
43
+ #: null model function
44
+ self.r_func_nullmodel = "feature ~ (1 + group | repetition)"
45
+
42
46
  self.set_options(model=model, feature=feature)
43
47
 
44
48
  # Make sure that lme4 is available
45
49
  if not rsetup.has_lme4():
46
- logger.info("Installing lme4, this may take a while!")
47
- rsetup.require_lme4()
50
+ warnings.warn("Installing lme4, this may take a while!",
51
+ Lme4InstallWarning)
52
+ rsetup.install_lme4()
53
+ rsetup.import_lme4()
48
54
 
49
55
  def add_dataset(self, ds, group, repetition):
50
56
  """Add a dataset to the analysis list
@@ -61,8 +67,8 @@ class Rlme4(object):
61
67
 
62
68
  Notes
63
69
  -----
64
- - For each repetition, there must be a "treatment" (``1``) and a
65
- "control" (``0``) group.
70
+ - For each repetition, there must be a "treatment" and a
71
+ "control" ``group``.
66
72
  - If you would like to perform a differential feature analysis,
67
73
  then you need to pass at least a reservoir and a channel
68
74
  dataset (with same parameters for `group` and `repetition`).
@@ -96,10 +102,10 @@ class Rlme4(object):
96
102
  The response variable is modeled using two linear mixed effect
97
103
  models:
98
104
 
99
- - model: "feature ~ group + (1 + group | repetition)"
100
- (random intercept + random slope model)
101
- - the null model: "feature ~ (1 + group | repetition)"
102
- (without the fixed effect introduced by the "treatment" group).
105
+ - model :const:`Rlme4.r_func_model` (random intercept +
106
+ random slope model)
107
+ - the null model :const:`Rlme4.r_func_nullmodel` (without
108
+ the fixed effect introduced by the "treatment" group).
103
109
 
104
110
  Both models are compared in R using "anova" (from the
105
111
  R-package "stats" :cite:`Everitt1992`) which performs a
@@ -127,16 +133,16 @@ class Rlme4(object):
127
133
  results: dict
128
134
  Dictionary with the results of the fitting process:
129
135
 
130
- - "anova p-value": Anova likelihood ratio test (significance)
136
+ - "anova p-value": Anova likelyhood ratio test (significance)
131
137
  - "feature": name of the feature used for the analysis
132
138
  ``self.feature``
133
139
  - "fixed effects intercept": Mean of ``self.feature`` for all
134
140
  controls; In the case of the "glmer+loglink" model, the intercept
135
- is already back transformed from log space.
141
+ is already backtransformed from log space.
136
142
  - "fixed effects treatment": The fixed effect size between the mean
137
143
  of the controls and the mean of the treatments relative to
138
144
  "fixed effects intercept"; In the case of the "glmer+loglink"
139
- model, the fixed effect is already back transformed from log
145
+ model, the fixed effect is already backtransformed from log
140
146
  space.
141
147
  - "fixed effects repetitions": The effects (intercept and
142
148
  treatment) for each repetition. The first axis defines
@@ -153,10 +159,11 @@ class Rlme4(object):
153
159
  - "model": model name used for the analysis ``self.model``
154
160
  - "model converged": boolean indicating whether the model
155
161
  converged
156
- - "r model summary": Summary of the model
157
- - "r model coefficients": Model coefficient table
158
- - "r script": the R script used
159
- - "r output": full output of the R script
162
+ - "r anova": Anova model (exposed from R)
163
+ - "r model summary": Summary of the model (exposed from R)
164
+ - "r model coefficients": Model coefficient table (exposed from R)
165
+ - "r stderr": errors and warnings from R
166
+ - "r stdout": standard output from R
160
167
  """
161
168
  self.set_options(model=model, feature=feature)
162
169
  self.check_data()
@@ -175,38 +182,105 @@ class Rlme4(object):
175
182
  groups.append(dd[1])
176
183
  repetitions.append(dd[2])
177
184
 
178
- # concatenate and populate arrays for R
179
- features_c = np.concatenate(features)
180
- groups_c = np.zeros(len(features_c), dtype=str)
181
- repetitions_c = np.zeros(len(features_c), dtype=int)
182
- pos = 0
183
- for ii in range(len(features)):
184
- size = len(features[ii])
185
- groups_c[pos:pos+size] = groups[ii][0]
186
- repetitions_c[pos:pos+size] = repetitions[ii]
187
- pos += size
188
-
189
- # Run R with the given template script
190
- rscript = importlib_resources.read_text("dclab.lme4",
191
- "lme4_template.R")
192
- _, script_path = tempfile.mkstemp(prefix="dclab_lme4_", suffix=".R",
193
- text=True)
194
- script_path = pathlib.Path(script_path)
195
- rscript = rscript.replace("<MODEL_NAME>", self.model)
196
- rscript = rscript.replace("<FEATURES>", arr2str(features_c))
197
- rscript = rscript.replace("<REPETITIONS>", arr2str(repetitions_c))
198
- rscript = rscript.replace("<GROUPS>", arr2str(groups_c))
199
- script_path.write_text(rscript, encoding="utf-8")
200
-
201
- result = rsetup.run_command((rsetup.get_r_script_path(), script_path))
202
-
203
- ret_dict = self.parse_result(result)
204
- ret_dict["is differential"] = self.is_differential()
205
- ret_dict["feature"] = self.feature
206
- ret_dict["r script"] = rscript
207
- ret_dict["r output"] = result
208
- assert ret_dict["model"] == self.model
209
-
185
+ # Fire up R
186
+ with rsetup.AutoRConsole() as ac:
187
+ r = rpy2.robjects.r
188
+
189
+ # Load lme4
190
+ rpy2.robjects.packages.importr("lme4")
191
+
192
+ # Concatenate huge arrays for R
193
+ r_features = rpy2.robjects.FloatVector(np.concatenate(features))
194
+ _groups = []
195
+ _repets = []
196
+ for ii in range(len(features)):
197
+ _groups.append(np.repeat(groups[ii], len(features[ii])))
198
+ _repets.append(np.repeat(repetitions[ii], len(features[ii])))
199
+ r_groups = rpy2.robjects.StrVector(np.concatenate(_groups))
200
+ r_repetitions = rpy2.robjects.IntVector(np.concatenate(_repets))
201
+
202
+ # Register groups and repetitions
203
+ rpy2.robjects.globalenv["feature"] = r_features
204
+ rpy2.robjects.globalenv["group"] = r_groups
205
+ rpy2.robjects.globalenv["repetition"] = r_repetitions
206
+
207
+ # Create a dataframe which contains all the data
208
+ r_data = r["data.frame"](r_features, r_groups, r_repetitions)
209
+
210
+ # Random intercept and random slope model
211
+ if self.model == 'glmer+loglink':
212
+ r_model = r["glmer"](self.r_func_model, r_data,
213
+ family=r["Gamma"](link='log'))
214
+ r_nullmodel = r["glmer"](self.r_func_nullmodel, r_data,
215
+ family=r["Gamma"](link='log'))
216
+ else: # lmer
217
+ r_model = r["lmer"](self.r_func_model, r_data)
218
+ r_nullmodel = r["lmer"](self.r_func_nullmodel, r_data)
219
+
220
+ # Anova analysis (increase verbosity by making models global)
221
+ # Using anova is a very conservative way of determining
222
+ # p values.
223
+ rpy2.robjects.globalenv["Model"] = r_model
224
+ rpy2.robjects.globalenv["NullModel"] = r_nullmodel
225
+ r_anova = r("anova(Model, NullModel)")
226
+ try:
227
+ pvalue = r_anova.rx2["Pr(>Chisq)"][1]
228
+ except ValueError: # rpy2 2.9.4
229
+ pvalue = r_anova[7][1]
230
+ r_model_summary = r["summary"](r_model)
231
+ r_model_coefficients = r["coef"](r_model)
232
+ try:
233
+ fe_reps = np.array(r_model_coefficients.rx2["repetition"])
234
+ except ValueError: # rpy2 2.9.4
235
+ fe_reps = np.concatenate((
236
+ np.array(r_model_coefficients[0][0]).reshape(1, -1),
237
+ np.array(r_model_coefficients[0][1]).reshape(1, -1)),
238
+ axis=0)
239
+
240
+ r_effects = r["data.frame"](r["coef"](r_model_summary))
241
+ try:
242
+ fe_icept = r_effects.rx2["Estimate"][0]
243
+ fe_treat = r_effects.rx2["Estimate"][1]
244
+ except ValueError: # rpy2 2.9.4
245
+ fe_icept = r_effects[0][0]
246
+ fe_treat = r_effects[0][1]
247
+ if self.model == "glmer+loglink":
248
+ # transform back from log
249
+ fe_treat = np.exp(fe_icept + fe_treat) - np.exp(fe_icept)
250
+ fe_icept = np.exp(fe_icept)
251
+ fe_reps[:, 1] = np.exp(fe_reps[:, 0] + fe_reps[:, 1]) \
252
+ - np.exp(fe_reps[:, 0])
253
+ fe_reps[:, 0] = np.exp(fe_reps[:, 0])
254
+
255
+ # convergence
256
+ try:
257
+ lme4l = r_model_summary.rx2["optinfo"].rx2["conv"].rx2["lme4"]
258
+ except ValueError: # rpy2 2.9.4
259
+ lme4l = r_model_summary[17][3][1]
260
+
261
+ if lme4l and "code" in lme4l.names:
262
+ try:
263
+ conv_code = lme4l.rx2["code"]
264
+ except ValueError: # rpy2 2.9.4
265
+ conv_code = lme4l[0]
266
+ else:
267
+ conv_code = 0
268
+
269
+ ret_dict = {
270
+ "anova p-value": pvalue,
271
+ "feature": self.feature,
272
+ "fixed effects intercept": fe_icept,
273
+ "fixed effects treatment": fe_treat, # aka "fixed effect"
274
+ "fixed effects repetitions": fe_reps,
275
+ "is differential": self.is_differential(),
276
+ "model": self.model,
277
+ "model converged": conv_code == 0,
278
+ "r anova": r_anova,
279
+ "r model summary": r_model_summary,
280
+ "r model coefficients": r_model_coefficients,
281
+ "r stderr": ac.get_warnerrors(),
282
+ "r stdout": ac.get_prints(),
283
+ }
210
284
  return ret_dict
211
285
 
212
286
  def get_differential_dataset(self):
@@ -214,7 +288,7 @@ class Rlme4(object):
214
288
 
215
289
  The most famous use case is differential deformation. The idea
216
290
  is that you cannot tell what the difference in deformation
217
- from channel to reservoir, because you never measure the
291
+ from channel to reservoir is, because you never measure the
218
292
  same object in the reservoir and the channel. You usually just
219
293
  have two distributions. Comparing distributions is possible
220
294
  via bootstrapping. And then, instead of running the lme4
@@ -288,34 +362,6 @@ class Rlme4(object):
288
362
  else:
289
363
  return False
290
364
 
291
- def parse_result(self, result):
292
- resd = result.split("OUTPUT")
293
- ret_dict = {}
294
- for item in resd:
295
- string = item.split("#*#")[0]
296
- key, value = string.split(":", 1)
297
- key = key.strip()
298
- value = value.strip().replace("\n\n", "\n")
299
-
300
- if key == "fixed effects repetitions":
301
- rows = value.split("\n")[1:]
302
- reps = []
303
- for row in rows:
304
- reps.append([float(vv) for vv in row.split()[1:]])
305
- value = np.array(reps).transpose()
306
- elif key == "model converged":
307
- value = value == "TRUE"
308
- elif value == "NA":
309
- value = np.nan
310
- else:
311
- try:
312
- value = float(value)
313
- except ValueError:
314
- pass
315
-
316
- ret_dict[key] = value
317
- return ret_dict
318
-
319
365
  def set_options(self, model=None, feature=None):
320
366
  """Set analysis options"""
321
367
  if model is not None:
@@ -326,16 +372,6 @@ class Rlme4(object):
326
372
  self.feature = feature
327
373
 
328
374
 
329
- def arr2str(a):
330
- """Convert an array to a string"""
331
- if isinstance(a.dtype.type, np.integer):
332
- return ",".join(str(dd) for dd in a.tolist())
333
- elif a.dtype.type == np.str_:
334
- return ",".join(f"'{dd}'" for dd in a.tolist())
335
- else:
336
- return ",".join(f"{dd:.16g}" for dd in a.tolist())
337
-
338
-
339
375
  def bootstrapped_median_distributions(a, b, bs_iter=1000, rs=117):
340
376
  """Compute the bootstrapped distributions for two arrays.
341
377
 
@@ -345,7 +381,7 @@ def bootstrapped_median_distributions(a, b, bs_iter=1000, rs=117):
345
381
  Input data
346
382
  bs_iter: int
347
383
  Number of bootstrapping iterations to perform
348
- (output size).
384
+ (outtput size).
349
385
  rs: int
350
386
  Random state seed for random number generator
351
387
 
@@ -360,7 +396,7 @@ def bootstrapped_median_distributions(a, b, bs_iter=1000, rs=117):
360
396
 
361
397
  Notes
362
398
  -----
363
- From a programmatic point of view, it would have been better
399
+ From a programmatical point of view, it would have been better
364
400
  to implement this method for just one input array (because of
365
401
  redundant code). However, due to historical reasons (testing
366
402
  and comparability to Shape-Out 1), bootstrapping is done
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dclab
3
- Version: 0.61.2
3
+ Version: 2.18.0
4
4
  Summary: Library for real-time deformability cytometry (RT-DC)
5
5
  Author: Benedikt Hartmann, Eoghan O'Connell, Maik Herbig, Maximilian Schlögel, Nadia Sbaa, Paul Müller, Philipp Rosendahl, Raghava Alajangi
6
6
  Maintainer-email: Paul Müller <dev@craban.de>
@@ -23,7 +23,7 @@ Requires-Dist: importlib-resources>=6.0
23
23
  Requires-Dist: numpy<3,>=1.21
24
24
  Requires-Dist: scipy<2,>=1.10.0
25
25
  Provides-Extra: all
26
- Requires-Dist: dclab[dcor,export,http,s3,tdms]; extra == "all"
26
+ Requires-Dist: dclab[dcor,export,http,lme4,s3,tdms]; extra == "all"
27
27
  Provides-Extra: dcor
28
28
  Requires-Dist: requests<3,>=2.31.0; extra == "dcor"
29
29
  Provides-Extra: export
@@ -31,6 +31,8 @@ Requires-Dist: fcswrite>=0.5.0; extra == "export"
31
31
  Requires-Dist: imageio[ffmpeg]; extra == "export"
32
32
  Provides-Extra: http
33
33
  Requires-Dist: requests<3,>=2.31.0; extra == "http"
34
+ Provides-Extra: lme4
35
+ Requires-Dist: rpy2>=2.9.4; extra == "lme4"
34
36
  Provides-Extra: s3
35
37
  Requires-Dist: boto3>=1.34.31; extra == "s3"
36
38
  Provides-Extra: tdms
@@ -1,5 +1,5 @@
1
1
  dclab/kde_methods.py,sha256=awlqkj819VRJzArG6Fx6myDa6qGpLoZ8S9IJ1J1YECA,9206
2
- dclab/_version.py,sha256=jCcQYeQUVxJlyYParhmvl3GfmgLyh6_duOusvZWWoSs,413
2
+ dclab/_version.py,sha256=QyMxKDzFsqXDA7bVrQozJznaASbsECKe57aBBNAljsA,413
3
3
  dclab/util.py,sha256=FcRsWcyKkKkfX2nZCShjJ5wPccr7U7NHsIDd_WEws6w,5141
4
4
  dclab/downsampling.pyx,sha256=OK7zbgGLl5gVyoU8ZBHo9EWwb8C9ChavmLNEvQvC9T0,7258
5
5
  dclab/__init__.py,sha256=1mskJAUo8HbvDhJRJFbcFB6HccFeqoRUCEHLuS64t_g,812
@@ -9,7 +9,7 @@ dclab/http_utils.py,sha256=TGqgiaUV-Z6Wiae__Yze-uHRWX57846ZnlV10ajzv2E,10562
9
9
  dclab/polygon_filter.py,sha256=qexmo-rXe06CUPZhN6EMJy4y4B5gXZeqejdvIB2arOE,13480
10
10
  dclab/statistics.py,sha256=tJDqPlY_Jw2Hhl-s7ugMBSZAxcRuPu4LQuBAZBXz7t8,6355
11
11
  dclab/kde_contours.py,sha256=5K7PzZz-FtSGvW4IR2tLpbEKKqCSsSTQPsupu7zIsPg,6745
12
- dclab/downsampling.cpython-312-darwin.so,sha256=zM2vp6uhNKEm3683X3C1agPoTF4eVwi0SZy2ol1aVL8,259136
12
+ dclab/downsampling.cpython-312-darwin.so,sha256=8Mu_2whAnEOv_GSzu97Po69Nt1ccewisS-Iqp5C4iO0,259136
13
13
  dclab/isoelastics/iso_LE-2D-FEM-19-volume-deform.txt,sha256=vTcazOlOXo3BQ0NQtGB_IdHKA0neOLXZ_d3JuMU--RE,83358
14
14
  dclab/isoelastics/iso_HE-3D-FEM-22-area_um-deform.txt,sha256=IjULG1KO-hClaYPoJJnwPbF2TkS9i9jxF1tbhhQTClY,71350
15
15
  dclab/isoelastics/iso_HE-2D-FEM-22-volume-deform.txt,sha256=aLOWxev_hvyP2yWquomcogn3Ze4SBfvqz-qmTOOibNw,97512
@@ -34,10 +34,10 @@ dclab/features/emodulus/scale_linear.py,sha256=5tZfOpG4QSSEMlwSoA_3XRQuY0tiZ3CSm
34
34
  dclab/features/emodulus/lut_HE-3D-FEM-22.txt,sha256=1Sg4ys0ykT7Q_jFM28XS0uJ85nZd_ha7BDXdN2rsPIA,47248
35
35
  dclab/features/emodulus/load.py,sha256=Q9rII-7Om-f0m183VNeJUxNno7at1reigKtbzdrWgjE,8557
36
36
  dclab/features/emodulus/viscosity.py,sha256=aBm1pQa9cFAOBnO07UWMj87bw6fQpmW1IlWWD0MEtgw,9438
37
- dclab/lme4/lme4_template.R,sha256=CEXQIquvYCla9dCvRYgiBemI6fiVgAKnJTetJA2LAtk,2570
38
- dclab/lme4/__init__.py,sha256=dj9ZR9WrxIXi704kOVsT2FCRkMhQMmp106mDXMP-aoI,256
39
- dclab/lme4/rsetup.py,sha256=emTCJavJAjKLy54B2qjQEw4_h3FXD6oUp0BRieon7EY,5036
40
- dclab/lme4/wrapr.py,sha256=rdIc2hS8GhgdU9WFA6pLzohJGlBga-mkm60qqqk6VO4,15017
37
+ dclab/lme4/__init__.py,sha256=Y_oqYEqNnHCjxfdzkoP0ZXPCQx6XSKqwt1pAgdGF2dA,235
38
+ dclab/lme4/rsetup.py,sha256=siVWXt0niXAqD20d2quOjVzaZc4-zF0i81G_HRZE6q0,6951
39
+ dclab/lme4/wrapr.py,sha256=MzJDUZ7L_5xDWVtl5vrDgTJoum2vrxfP0dYwPMcHIPY,16977
40
+ dclab/lme4/rlibs.py,sha256=3oHSrSykeQB9ldvrV4Ite9EffIURUwCo3bn4uBpVeSk,2539
41
41
  dclab/cli/task_tdms2rtdc.py,sha256=u0L1Fq9rXIeQG9b72SuUIh_qYC6fG2xXxht9_rcdCao,8283
42
42
  dclab/cli/__init__.py,sha256=84YzzV6aE_NY-o7wvqgvUoxBLvIOEXpSUbkVcGRyzQ0,483
43
43
  dclab/cli/task_condense.py,sha256=uNZzm04VuQOXJi6uXPmaLdQCk0g8ONueiO4p67yJv0k,8546
@@ -61,17 +61,17 @@ dclab/external/statsmodels/nonparametric/kernel_density.py,sha256=3UyzLuZS68TkNT
61
61
  dclab/external/statsmodels/nonparametric/__init__.py,sha256=-GEWgwsF27ems5WTBvR1zo4SWJ0pRTWyHVagnIYer3g,43
62
62
  dclab/external/statsmodels/nonparametric/kernels.py,sha256=fuy4kStFz2ZA9pqgfUb4cly-YBpXLu4TJ9-ZujayuIw,1075
63
63
  dclab/external/skimage/measure.py,sha256=y1idCqD9TUxp3-QnOiWR_d674OKaeqBJ4MN2-gVP6ro,247
64
- dclab/external/skimage/_pnpoly.cpython-312-darwin.so,sha256=S8m8J1d4qVSQ8ht4_vF5T03jrW5ix7bHrDiYatwJn44,222776
64
+ dclab/external/skimage/_pnpoly.cpython-312-darwin.so,sha256=QevOsMePM73uwzfnRPAVcgnFs_thMF1iN9R2N3Ksy64,222776
65
65
  dclab/external/skimage/LICENSE,sha256=ivsSBvn3c0R9mOctWRRdza7C7wdZSRYgCVxlVqUdlB8,1452
66
66
  dclab/external/skimage/pnpoly.py,sha256=r8hFNiTz5XlUoNZjosqA0iyv1FPn0l7ewbplgFgkdaw,1347
67
67
  dclab/external/skimage/_find_contours.py,sha256=16v5eeTZBmevG8SSuXtJ6yUpVPhwfSmtc8pDD0nuuOU,9340
68
68
  dclab/external/skimage/__init__.py,sha256=-B2QUKHAFzQuBWuuKvPDC5JIl0Zb-x3OGmbwPaE9VwQ,72
69
- dclab/external/skimage/_find_contours_cy.cpython-312-darwin.so,sha256=5fTIJOXpu8iFA5GDMC4WM_7dhQeRyx3QLy53DSK73M8,204520
69
+ dclab/external/skimage/_find_contours_cy.cpython-312-darwin.so,sha256=2kub7ht3_Dx6HDfdLa5-RTkeeXG_-nai4ooY_vtiAfQ,204520
70
70
  dclab/external/skimage/_pnpoly.pyx,sha256=Qdn6xPazDschBqbr46DzB75MB2MnqvdnoTSBMK7kUGE,2504
71
71
  dclab/external/skimage/_find_contours_cy.pyx,sha256=pZJOBhMHzYEMkcz4WQVyjn7jDNrdjCfet47FU1hRAxk,7161
72
72
  dclab/external/skimage/_shared/geometry.pxd,sha256=kRsu9ifv_rL3kbRIgSLf86p0hn2oTMp6s013lZ9bBZM,346
73
73
  dclab/external/skimage/_shared/__init__.py,sha256=2sHZwTtJSlMTa3Q2YSvQW7jrPLMUSqDJQa-ROe5zfcw,37
74
- dclab/external/skimage/_shared/geometry.cpython-312-darwin.so,sha256=wia_nS-MBJy17QrJIbwkJ2x2SA_zp6oT0SwanKzDDKo,39216
74
+ dclab/external/skimage/_shared/geometry.cpython-312-darwin.so,sha256=CdXgHOQZTU-tUUsnE7TALAj_5g3jK9KVvbXEfVqk8RI,39216
75
75
  dclab/external/skimage/_shared/geometry.pyx,sha256=miCHUh6mBDbRRIoaF_0xAER1MRzsCAzFdlYQZhV7RmE,1667
76
76
  dclab/definitions/feat_logic.py,sha256=SXsSlAusgtE3uXcPu84dQwYZ07zxmV37DmPednA3_dM,5823
77
77
  dclab/definitions/meta_parse.py,sha256=YdaTdM8DAMsIFn5ITH9OHYGTXeAOBGWtx22oVjxXcWk,2393
@@ -130,9 +130,9 @@ dclab/rtdc_dataset/fmt_tdms/event_image.py,sha256=-jp7Z-N91e4ieumYQ1huMicj7PMJqw
130
130
  dclab/rtdc_dataset/fmt_tdms/event_trace.py,sha256=Vkym0QKSw2mq1XZl5n8wDkgHXmaZwQGiMAV5AuRSJkE,5215
131
131
  dclab/rtdc_dataset/fmt_tdms/exc.py,sha256=WzrMqnyrzp8gsT8Pf7JKqGGv43ewx7d_qgtirURppRI,813
132
132
  dclab/rtdc_dataset/fmt_tdms/event_contour.py,sha256=kjo0wJx9F0gmmOOyR0NoLw6VEtSl3h63WXXkcbfnoS8,9627
133
- dclab-0.61.2.dist-info/RECORD,,
134
- dclab-0.61.2.dist-info/LICENSE,sha256=1mLfjOTOaeiMSGPJiF5hHnMQfKX88QVeZpCCXwJGj3k,18131
135
- dclab-0.61.2.dist-info/WHEEL,sha256=Gslco0u-UdyhQslriEMCnIh5wENI6Dd1FlwyhvZM_HQ,110
136
- dclab-0.61.2.dist-info/entry_points.txt,sha256=eOpjgznu-eW-9utUpLU-77O5098YyUEgGF3ksGMdtec,273
137
- dclab-0.61.2.dist-info/top_level.txt,sha256=irvwZMgs1edY1Zj60ZFk7Almb9Zhk4k6E6aC4YPFnnM,6
138
- dclab-0.61.2.dist-info/METADATA,sha256=QibArMbIi19J5t670ST0BhxxT94ruSFTQ5J2nif3F18,4754
133
+ dclab-2.18.0.dist-info/RECORD,,
134
+ dclab-2.18.0.dist-info/LICENSE,sha256=1mLfjOTOaeiMSGPJiF5hHnMQfKX88QVeZpCCXwJGj3k,18131
135
+ dclab-2.18.0.dist-info/WHEEL,sha256=Gslco0u-UdyhQslriEMCnIh5wENI6Dd1FlwyhvZM_HQ,110
136
+ dclab-2.18.0.dist-info/entry_points.txt,sha256=eOpjgznu-eW-9utUpLU-77O5098YyUEgGF3ksGMdtec,273
137
+ dclab-2.18.0.dist-info/top_level.txt,sha256=irvwZMgs1edY1Zj60ZFk7Almb9Zhk4k6E6aC4YPFnnM,6
138
+ dclab-2.18.0.dist-info/METADATA,sha256=WTNyL58XdjHtE11bxXQWGntyi981Fv5pWGaxXshj1Iw,4824
@@ -1,94 +0,0 @@
1
- require(stats);
2
- require(lme4);
3
-
4
- model_name <- "<MODEL_NAME>"
5
- cat("OUTPUT model:", model_name, "#*#\n")
6
-
7
- func_model <- "feature ~ group + (1 + group | repetition)"
8
- func_nullmodel <- "feature ~ (1 + group | repetition)"
9
-
10
- # These are the feature, group, and repetition arrays that are set by dclab
11
- # via templates.
12
- feature <- c(<FEATURES>)
13
- group <- c(<GROUPS>)
14
- repetition <- c(<REPETITIONS>)
15
-
16
- data <- data.frame(feature, group, repetition)
17
-
18
- if (model_name == "glmer+loglink") {
19
- Model <- glmer(func_model, data, family=Gamma(link='log'))
20
- NullModel <- glmer(func_nullmodel, data, family=Gamma(link='log'))
21
- } else if (model_name == "lmer") {
22
- Model <- lmer(func_model, data)
23
- NullModel <- lmer(func_nullmodel, data)
24
- } else {
25
- stop("Invalid model_name:", model_name)
26
- }
27
-
28
- # Anova analysis (increase verbosity by making models global)
29
- # Using anova is a very conservative way of determining
30
- # p values.
31
- res_anova <- anova(Model, NullModel)
32
- cat("OUTPUT r anova: ")
33
- res_anova
34
- cat("#*#\n")
35
-
36
- pvalue <- res_anova$"Pr(>Chisq)"[2]
37
- cat("OUTPUT anova p-value:", pvalue, "#*#\n")
38
-
39
- model_summary <- summary(Model)
40
- cat("OUTPUT r model summary:")
41
- model_summary
42
- cat("#*#\n")
43
-
44
- model_coefficients <- coef(Model)
45
- cat("OUTPUT r model coefficients:")
46
- model_coefficients
47
- cat("#*#\n")
48
-
49
- fe_reps <- model_coefficients$repetition
50
-
51
- effects <- data.frame(coef(model_summary))
52
-
53
- fe_icept <- effects$Estimate[1]
54
-
55
- fe_treat <- effects$Estimate[2]
56
-
57
- if (model_name == "glmer+loglink") {
58
- # transform back from log
59
- fe_treat <- exp(fe_icept + fe_treat) - exp(fe_icept)
60
- fe_icept <- exp(fe_icept)
61
- fe_reps[, 2] = exp(fe_reps[, 1] + fe_reps[, 2]) - exp(fe_reps[, 1])
62
- fe_reps[, 1] = exp(fe_reps[, 1])
63
- }
64
-
65
- cat("OUTPUT fixed effects intercept:", fe_icept, "#*#\n")
66
- cat("OUTPUT fixed effects treatment:", fe_treat, "#*#\n")
67
- cat("OUTPUT fixed effects repetitions:")
68
- fe_reps
69
- cat("#*#\n")
70
-
71
- # convergence
72
-
73
- # convergence warnings in lme4
74
- is_warning_generated <- function(m) {
75
- df <- summary(m)
76
- !is.null(df$optinfo$conv$lme4$messages) &&
77
- grepl('failed to converge', df$optinfo$conv$lme4$messages)
78
- }
79
- lme4_not_converged <- is_warning_generated(Model)
80
-
81
- # convergence code by the optimizer
82
- lme4l <- model_summary$optinfo$conv$lme4
83
- if (length(lme4l) == 0) {
84
- # the optimizer probably does not know
85
- conv_code <- 0
86
- } else if (is.null(lme4l$code)) {
87
- # NULL means 0
88
- conv_code <- 0
89
- } else {
90
- conv_code <- lme4l$code
91
- }
92
-
93
- cat("OUTPUT model converged:", (conv_code == 0) && !lme4_not_converged, "#*#\n")
94
- cat("OUTPUT lme4 messages:", lme4l$optinfo$conv$lme4$messages, "#*#\n")
File without changes