nctoolkit 1.1.10__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. nctoolkit-1.2.0/CONTRIBUTIONS.md +39 -0
  2. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/PKG-INFO +1 -2
  3. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/cheatsheet/nctoolkit_cheatsheet.pdf +0 -0
  4. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/cheatsheet/nctoolkit_cheatsheet.pptx +0 -0
  5. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/__init__.py +1 -1
  6. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/assign.py +2 -0
  7. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/cleanup.py +4 -4
  8. nctoolkit-1.2.0/nctoolkit/create_ensemble.py +117 -0
  9. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/plot.py +0 -2
  10. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/runners.py +11 -1
  11. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/setters.py +6 -5
  12. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/static_plot.py +52 -7
  13. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/subset.py +11 -5
  14. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/toxarray.py +11 -2
  15. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/verticals.py +3 -2
  16. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit.egg-info/SOURCES.txt +1 -0
  17. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/setup.py +1 -2
  18. nctoolkit-1.1.10/nctoolkit/create_ensemble.py +0 -57
  19. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/CITATION.cff +0 -0
  20. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/CODE_OF_CONDUCT.md +0 -0
  21. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/LICENSE +0 -0
  22. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/MANIFEST.in +0 -0
  23. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/README.md +0 -0
  24. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/benchmarks/benchmark_nctoolkit_versus_xarray.ipynb +0 -0
  25. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/checklists/api_checker.ipynb +0 -0
  26. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/data/geotiff.tif +0 -0
  27. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/data/pubplot_test.png +0 -0
  28. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/data/test1.html +0 -0
  29. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/data/test2.html +0 -0
  30. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/add_etc.py +0 -0
  31. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/anomaly.py +0 -0
  32. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/api.py +0 -0
  33. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/append.py +0 -0
  34. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/cdo_command.py +0 -0
  35. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/cellareas.py +0 -0
  36. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/centres.py +0 -0
  37. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/checks.py +0 -0
  38. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/clear.py +0 -0
  39. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/compare.py +0 -0
  40. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/compare_data.py +0 -0
  41. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/corr.py +0 -0
  42. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/crop.py +0 -0
  43. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/deprecated.py +0 -0
  44. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/distgrid.py +0 -0
  45. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/drop.py +0 -0
  46. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/ensembles.py +0 -0
  47. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/esoteric.py +0 -0
  48. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/experimental.py +0 -0
  49. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/fill.py +0 -0
  50. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/flatten.py +0 -0
  51. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/fldstat.py +0 -0
  52. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/format.py +0 -0
  53. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/generate_grid.py +0 -0
  54. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/inttime.py +0 -0
  55. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/invert.py +0 -0
  56. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/masking.py +0 -0
  57. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/matchpoint.py +0 -0
  58. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/mergers.py +0 -0
  59. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/meridonials.py +0 -0
  60. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/mp_adders.py +0 -0
  61. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/mp_matchers.py +0 -0
  62. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/mp_matchups.py +0 -0
  63. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/nco_command.py +0 -0
  64. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/phenology.py +0 -0
  65. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/reduce.py +0 -0
  66. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/reduce_grid.py +0 -0
  67. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/regrid.py +0 -0
  68. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/remove.py +0 -0
  69. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/rename.py +0 -0
  70. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/resample.py +0 -0
  71. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/rollstat.py +0 -0
  72. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/run.py +0 -0
  73. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/runthis.py +0 -0
  74. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/session.py +0 -0
  75. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/set.py +0 -0
  76. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/shape.py +0 -0
  77. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/shift.py +0 -0
  78. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/show.py +0 -0
  79. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/split.py +0 -0
  80. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/strip_vars.py +0 -0
  81. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/sumall.py +0 -0
  82. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/temp_file.py +0 -0
  83. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/temporal_stat.py +0 -0
  84. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/temporals.py +0 -0
  85. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/thresholds.py +0 -0
  86. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/to_lonlat.py +0 -0
  87. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/to_nc.py +0 -0
  88. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/tozlev.py +0 -0
  89. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/unify.py +0 -0
  90. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/utils.py +0 -0
  91. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/validator.py +0 -0
  92. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/validator_funs.py +0 -0
  93. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/zip.py +0 -0
  94. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/nctoolkit/zonals.py +0 -0
  95. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/requirements.txt +0 -0
  96. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/setup.cfg +0 -0
  97. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/testing/test_parallel.py +0 -0
  98. {nctoolkit-1.1.10 → nctoolkit-1.2.0}/testing/test_scripting.py +0 -0
@@ -0,0 +1,39 @@
1
+ Contributing to nctoolkit
2
+
3
+ 🎉 Thank you for your interest in contributing to nctoolkit! Your efforts help improve this tool for the entire community. Whether you're fixing bugs, adding new features, or enhancing documentation, your contributions are invaluable. Let's make this project even better together! 🎉
4
+
5
+ How to Contribute
6
+
7
+ To contribute, please follow these steps:
8
+
9
+ 1. Fork the Repository
10
+ Click the "Fork" button at the top right of this page to create a copy of this repository under your GitHub account.
11
+
12
+ 2. Clone and Set Up the Repository
13
+ After forking, open your terminal, clone the repository to your local machine, and navigate into the project directory:
14
+ git clone https://github.com/your-username/nctoolkit.git
15
+ cd nctoolkit
16
+
17
+ 3. Create a New Branch
18
+ Create a new branch for your contribution to keep your changes organized and separated from the main codebase:
19
+ git checkout -b feature/your-feature-name
20
+
21
+ 4. Make Your Changes
22
+ Add your feature, fix bugs, or improve documentation. Be sure to:
23
+ Keep your changes focused on a single feature or fix for easier review.
24
+ Test your modifications to ensure they work as intended.
25
+ Follow any coding style guidelines noted in the project.
26
+
27
+ 5. Commit Your Changes
28
+ When you’re ready, stage and commit your changes with a clear, descriptive commit message:
29
+ git add .
30
+ git commit -m "Add detailed description of your changes here"
31
+
32
+ 6. Push to Your Fork
33
+ Push your branch to your GitHub repository:
34
+ git push origin feature/your-feature-name
35
+
36
+ 7. Submit a Pull Request (PR)
37
+ Go to the original nctoolkit repository on GitHub.
38
+ Click the "New Pull Request" button and select your branch.
39
+ Include a detailed description of your changes to help reviewers understand your contribution.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nctoolkit
3
- Version: 1.1.10
3
+ Version: 1.2.0
4
4
  Summary: A Python package for netCDF analysis and post-processing
5
5
  Home-page: https://github.com/pmlmodelling/nctoolkit
6
6
  Author: Robert Wilson
@@ -56,7 +56,6 @@ Core abilities of nctoolkit include:
56
56
  - Calculating the correlations between variables
57
57
  - Calculating vertical statistics for the likes of oceanic data
58
58
  - Calculating ensemble statistics
59
- - Calculating phenological metrics
60
59
 
61
60
  Operation of the package requires the installation of Climate Data Operators (CDO). This is the computational backend for most of the methods used. No knowledge of CDO is required to use nctoolkit. A couple of methods provide users with the option of using netCDF Operators (NCO) as a backend. Guidance for how to install the backends are available `here <https://nctoolkit.readthedocs.io/en/stable/installing.html>`__.
62
61
 
@@ -28,7 +28,7 @@ atexit.register(clean_all)
28
28
 
29
29
  signal.signal(signal.SIGTERM, clean_all)
30
30
 
31
- from nctoolkit.create_ensemble import create_ensemble
31
+ from nctoolkit.create_ensemble import create_ensemble, glob
32
32
  from nctoolkit.session import session_files
33
33
  from nctoolkit.show import nc_variables, nc_years, nc_months, nc_times
34
34
 
@@ -91,6 +91,8 @@ funs = [
91
91
  "exp",
92
92
  "ln",
93
93
  "sin",
94
+ "min",
95
+ "max",
94
96
  "cos",
95
97
  "tan",
96
98
  "int",
@@ -145,12 +145,12 @@ def temp_check():
145
145
  if len(mylist) > 0:
146
146
  if len(mylist) == 1:
147
147
  print(
148
- f"{len(mylist)} file was created by nctoolkit in prior or current "
148
+ f"{len(mylist)} temporary file was created by nctoolkit in prior or current "
149
149
  f"sessions. Consider running deep_clean!"
150
150
  )
151
151
  else:
152
152
  print(
153
- f"{len(mylist)} files were created by nctoolkit in prior or current"
153
+ f"{len(mylist)} temporary files were created by nctoolkit in prior or current"
154
154
  f" sessions. Consider running deep_clean!"
155
155
  )
156
156
 
@@ -163,12 +163,12 @@ def temp_check():
163
163
  if len(mylist) > 0:
164
164
  if len(mylist) == 1:
165
165
  print(
166
- f"{len(mylist)} file was created by nctoolkit in prior or current "
166
+ f"{len(mylist)} temporary file was created by nctoolkit in prior or current "
167
167
  f"sessions. Consider running deep_clean!"
168
168
  )
169
169
  else:
170
170
  print(
171
- f"{len(mylist)} files were created by nctoolkit in prior or "
171
+ f"{len(mylist)} temporary files were created by nctoolkit in prior or "
172
172
  f"current sessions. Consider running deep_clean!"
173
173
  )
174
174
 
@@ -0,0 +1,117 @@
1
+ from glob import glob as globber
2
+ import os
3
+
4
+ # function to find files in directory with a specified variable
5
+
6
+
7
+ def create_ensemble(path="", recursive=True):
8
+ """
9
+ create_ensemble: Generate an ensemble of files from a directory.
10
+
11
+ Parameters
12
+ -------------
13
+ path: str
14
+ The directory to search for netCDF files
15
+ recursive : boolean
16
+ True/False depending on whether you want to search the path recursively.
17
+ Defaults to True.
18
+
19
+ Returns
20
+ -------------
21
+
22
+ files : list of files
23
+
24
+ Examples
25
+ ------------
26
+
27
+ If you wanted to recursively find all netCDF files available in a directory "data", you would do this:
28
+
29
+ >>> import nctoolkit as nc
30
+ >>> nc.create_ensemble("data")
31
+
32
+ If you wanted to find the files in that directory and ignore subdirectories, you would instead do this:
33
+
34
+ >>> nc.create_ensemble("data", recursive = False)
35
+
36
+
37
+ """
38
+
39
+ # make sure the path exists
40
+
41
+ if os.path.exists(path) is False:
42
+ raise ValueError("The path provided does not exist!")
43
+
44
+ # make sure the path ends with "/" if it is not empty
45
+ if path != "":
46
+ if path.endswith("/") is False:
47
+ path = path + "/"
48
+
49
+ if recursive:
50
+ files = [f for f in globber(path + "/**/*.nc*", recursive=True)]
51
+ else:
52
+ files = [f for f in globber(path + "*.nc*")]
53
+
54
+ if len(files) == 0:
55
+ raise ValueError("There is no data in the target directory")
56
+
57
+ return files
58
+
59
+ def glob(path="", recursive=True):
60
+ """
61
+ create_ensemble: Generate an ensemble of files from a directory.
62
+
63
+ Parameters
64
+ -------------
65
+ path: str
66
+ The directory to search for netCDF files
67
+ recursive : boolean
68
+ True/False depending on whether you want to search the path recursively.
69
+ Defaults to True.
70
+
71
+ Returns
72
+ -------------
73
+
74
+ files : list of files
75
+
76
+ Examples
77
+ ------------
78
+
79
+ If you wanted to recursively find all netCDF files available in a directory "data", you would do this:
80
+
81
+ >>> import nctoolkit as nc
82
+ >>> nc.create_ensemble("data")
83
+
84
+ If you wanted to find the files in that directory and ignore subdirectories, you would instead do this:
85
+
86
+ >>> nc.create_ensemble("data", recursive = False)
87
+
88
+
89
+ """
90
+
91
+ # make sure the path exists
92
+
93
+ try:
94
+ files = globber(path, recursive = recursive)
95
+ if [x for x in files if x.endswith(".nc")]:
96
+ return files
97
+ else:
98
+ raise ValueError("There are no netCDF files in the target directory")
99
+ except:
100
+
101
+ if os.path.exists(path) is False:
102
+ raise ValueError("The path provided does not exist!")
103
+
104
+ # make sure the path ends with "/" if it is not empty
105
+ if path != "":
106
+ if path.endswith("/") is False:
107
+ path = path + "/"
108
+
109
+ if recursive:
110
+ files = [f for f in globber(path + "/**/*.nc*", recursive=True)]
111
+ else:
112
+ files = [f for f in globber(path + "*.nc*")]
113
+
114
+ if len(files) == 0:
115
+ raise ValueError("There is no data in the target directory")
116
+
117
+ return files
@@ -44,8 +44,6 @@ def plot(self, vars=None, autoscale=True, out=None, coast=None, **kwargs):
44
44
  A string or list of the variables to plot
45
45
  autoscale: bool
46
46
  Set to True if you want the colorbar to be scaled to the min/max of the data. Default is True
47
- vars: str , list
48
- A string or list of the variables to plot
49
47
  out: str
50
48
  Name of output file if you want to save as html. Defaults to None.
51
49
  coast: bool
@@ -22,6 +22,12 @@ from nctoolkit.temp_file import temp_file
22
22
 
23
23
  from nctoolkit.show import nc_variables
24
24
 
25
+ def ignore_warning(x):
26
+ text = re.compile("remap weights from .* not used")
27
+ if len(text.findall(x)) > 0:
28
+ return True
29
+ return False
30
+
25
31
  def ann_anomaly(
26
32
  ff, baseline, metric, window, align, precision, new_files, new_commands, nc_safe):
27
33
  """
@@ -840,10 +846,14 @@ def run_cdo(command=None, target=None, out_file=None, overwrite=False, precision
840
846
  else:
841
847
  message = f"The following month was missing in the dataset: {sel_month}"
842
848
  warnings.warn(message=message)
849
+ #remap weights from /tmp/nctoolkit_rwi_brtnahgznctoolkittmpqwadyf2y.nc not used, lonlat (984x582) grid with mask (407783) not found!
850
+ # detect regex pattern, based on above
851
+
843
852
  for ww in w:
844
853
  if platform.system() == "Linux":
845
854
  if ww.message not in session_warnings:
846
- session_warnings.append(ww.message)
855
+ if ignore_warning(str(ww.message)) is False:
856
+ session_warnings.append(ww.message)
847
857
  else:
848
858
  warnings.warn(ww.message)
849
859
  #print(session_warnings)
@@ -278,12 +278,13 @@ def set_units(self, unit_dict=None, **kwargs):
278
278
 
279
279
  # change the units in turn. This doesn't seem to be something you can chain?
280
280
  for i in unit_dict:
281
- if not isinstance(i, str):
282
- raise TypeError("key,values in unit_dict are not strings")
283
- if not isinstance(unit_dict[i], str):
284
- raise TypeError("key,values in unit_dict are not strings")
281
+ if unit_dict[i] is not None:
282
+ if not isinstance(i, str):
283
+ raise TypeError("key,values in unit_dict are not strings")
284
+ if not isinstance(unit_dict[i], str):
285
+ raise TypeError("key,values in unit_dict are not strings")
285
286
 
286
- cdo_command = f'-setattribute,{i}@units="{unit_dict[i]}"'
287
+ cdo_command = f'-setattribute,{i}@units="{unit_dict[str(i)]}"'
287
288
  self.cdo_command(cdo_command, ensemble=False)
288
289
 
289
290
 
@@ -93,6 +93,7 @@ def pub_plot(
93
93
  out=None,
94
94
  breaks=None,
95
95
  dpi = "figure",
96
+ font = None,
96
97
  **kwargs,
97
98
  ):
98
99
  """
@@ -360,8 +361,8 @@ def pub_plot(
360
361
 
361
362
  # set globe to True if lon lat spread is big enough
362
363
 
363
- if np.max(lon) - np.min(lon) > 358:
364
- if np.max(lat) - np.min(lat) > 176:
364
+ if np.max(lon) - np.min(lon) > 340:
365
+ if np.max(lat) - np.min(lat) > 160:
365
366
  globe = True
366
367
 
367
368
  # proj=ccrs.LambertConformal(central_longitude=np.mean(lon), central_latitude=np.mean(lat), false_easting=0.0, false_northing=0.0, cutoff=38)
@@ -388,6 +389,13 @@ def pub_plot(
388
389
 
389
390
  if projection == None:
390
391
  proj = ccrs.PlateCarree()
392
+
393
+ if isinstance(projection, ccrs.NorthPolarStereo) and size == "auto":
394
+ size = [8,8]
395
+ if isinstance(projection, ccrs.SouthPolarStereo) and size == "auto":
396
+ size = [8,8]
397
+ if isinstance(projection, ccrs.AzimuthalEquidistant) and size == "auto":
398
+ size = [12, 8]
391
399
  if size == "auto":
392
400
  size = [10, 10]
393
401
  if proj == ccrs.PlateCarree() or globe:
@@ -507,9 +515,13 @@ def pub_plot(
507
515
  if r_max is not None:
508
516
  vmax = np.nanpercentile(np.ma.filled(values, np.nan), r_max)
509
517
 
510
- if robust and limits is None:
518
+ if robust:
511
519
  vmin = np.nanpercentile(np.ma.filled(values, np.nan), 2)
512
520
  vmax = np.nanpercentile(np.ma.filled(values, np.nan), 98)
521
+ limits = [0,0]
522
+ limits[0] = vmin
523
+ limits[1] = vmax
524
+
513
525
  if limits is None:
514
526
  if r_min is not None:
515
527
  vmin = np.nanpercentile(np.ma.filled(values, np.nan), r_min)
@@ -684,10 +696,34 @@ def pub_plot(
684
696
  else:
685
697
  fraction = 0.046 * size[1] / size[0]
686
698
 
699
+ min_value = np.min(values)
700
+ max_value = np.max(values)
701
+
702
+ min_arrow = False
703
+
704
+ if vmin is not None:
705
+ if vmin > min_value:
706
+ min_arrow = True
707
+ max_arrow = False
708
+ if vmax is not None:
709
+ if vmax < max_value:
710
+ max_arrow = True
711
+
712
+ if min_arrow and max_arrow:
713
+ extend = "both"
714
+ else:
715
+ if min_arrow:
716
+ extend = "min"
717
+ else:
718
+ if max_arrow:
719
+ extend = "max"
720
+ else:
721
+ extend = "neither"
722
+
687
723
  if l_location == "bottom":
688
- cb = plt.colorbar(im, fraction=fraction, pad=0.04, location=l_location)
724
+ cb = plt.colorbar(im, fraction=fraction, pad=0.04, location=l_location, extend = extend)
689
725
  else:
690
- cb = plt.colorbar(im, fraction=fraction, pad=0.04)
726
+ cb = plt.colorbar(im, fraction=fraction, pad=0.04, extend = extend)
691
727
 
692
728
  # add breaks to colorbar cb
693
729
  if breaks is not None:
@@ -742,11 +778,12 @@ def pub_plot(
742
778
  "Unable to parse legend from dataset contents. Check long names and units"
743
779
  )
744
780
  label = ""
781
+ # label may need some slight tidying
745
782
 
746
783
  if l_location == "bottom":
747
784
  label = "\n".join(wrap(label, 120))
748
785
  else:
749
- label = "\n".join(wrap(label, 50))
786
+ label = "\n".join(wrap(label, 30))
750
787
 
751
788
  if l_location == "bottom":
752
789
  cbax.set_xlabel(label)
@@ -755,10 +792,18 @@ def pub_plot(
755
792
 
756
793
  if legend_position is None:
757
794
  cb.remove()
795
+
796
+ if font is not None:
797
+ for text in [ax.title, ax.xaxis.label, ax.yaxis.label, cb.ax.yaxis.label, cb.ax.xaxis.label]:
798
+ text.set_fontsize(font)
799
+ for item in ([ax.title, ax.xaxis.label, ax.yaxis.label, cb.ax.yaxis.label, cb.ax.xaxis.label] +
800
+ ax.get_xticklabels() + ax.get_yticklabels()):
801
+ item.set_fontsize(font)
758
802
 
759
803
  if out is not None:
760
804
  print("saving as file")
761
805
  plt.savefig(out, dpi = dpi)
806
+ plt.savefig(out, dpi = dpi)
762
807
 
763
808
 
764
809
  def quiver_plot(ds, u=None, v=None, **kwargs):
@@ -815,4 +860,4 @@ def quiver_plot(ds, u=None, v=None, **kwargs):
815
860
  if v not in vars:
816
861
  raise ValueError("v not in dataset")
817
862
 
818
- pub_plot(ds, quiver=True, u=u, v=v, **kwargs)
863
+ pub_plot(ds, quiver=True, u=u, v=v, **kwargs)
@@ -45,24 +45,30 @@ def select_levels(self, levels=None):
45
45
  levels : list
46
46
  List of the form [min_level, max_level]. Levels/depth between the two will be selected
47
47
  """
48
+ ds_levels = self.levels
49
+
48
50
  if not isinstance(levels, list):
49
51
  type(levels)
50
52
  try:
51
53
  levels = float(levels)
52
54
  except:
53
55
  raise ValueError("levels provided are not valid!")
56
+ levels = [float(levels), float(levels)]
54
57
 
55
58
  if isinstance(levels, list):
56
59
  try:
57
- levels[0] = int(np.floor(float(levels[0])))
58
- levels[1] = int(np.ceil(float(levels[1])))
60
+ levels[0] = float(levels[0])
61
+ levels[1] = float(levels[1])
59
62
  except:
60
63
  raise ValueError("levels provided are not valid!")
61
64
  if levels[0] > levels[1]:
62
65
  raise ValueError("levels have the wrong order")
63
- levels = f"{levels[0]}/{levels[1]}"
64
-
65
- cdo_command = f"-sellevel,{levels}"
66
+
67
+ sel_levels = [str(x) for x in ds_levels if levels[0] <= x <= levels[1]]
68
+ if len(sel_levels) == 0:
69
+ raise ValueError("No levels found in the dataset")
70
+ sel_levels = ",".join(sel_levels)
71
+ cdo_command = f"-sellevel,{sel_levels}"
66
72
 
67
73
  self.cdo_command(cdo_command, ensemble=False)
68
74
 
@@ -102,7 +102,7 @@ def to_xarray(self, decode_times=True, **kwargs):
102
102
  return data
103
103
 
104
104
 
105
- def to_dataframe(self, decode_times=True, **kwargs):
105
+ def to_dataframe(self, decode_times=True, drop_bnds = True, **kwargs):
106
106
  """
107
107
  to_dataframe: Convert a dataset to a pandas data frame
108
108
 
@@ -111,6 +111,10 @@ def to_dataframe(self, decode_times=True, **kwargs):
111
111
  decode_times: boolean
112
112
  Set to False if you do not want xarray to decode the times prior to
113
113
  conversion to data frame. Default is True.
114
+ drop_bnds: boolean
115
+ Set to False if you do not want to drop the bounds from the data frame.
116
+ Variables/coordinates with '_bnds' in their names will be dropped.
117
+ Default is True.
114
118
  **kwargs : kwargs
115
119
  Optional arguments to be sent to subset.
116
120
 
@@ -125,4 +129,9 @@ def to_dataframe(self, decode_times=True, **kwargs):
125
129
  >>> ds.to_dataframe()
126
130
 
127
131
  """
128
- return self.to_xarray(decode_times=decode_times, **kwargs).to_dataframe()
132
+ df = self.to_xarray(decode_times=decode_times, **kwargs).to_dataframe()
133
+ df = df.reset_index()
134
+ if drop_bnds:
135
+ df = df.drop(columns=[col for col in df.columns if 'bnds' in col])
136
+ df = df.drop_duplicates().reset_index(drop = True)
137
+ return df
@@ -180,6 +180,9 @@ def vertical_interp(
180
180
  if thickness is not None:
181
181
  fixed = False
182
182
 
183
+ if isinstance(levels, (int, float)):
184
+ levels = [levels]
185
+
183
186
  if fixed is False:
184
187
  self.to_zlevels(
185
188
  levels=levels, thickness=thickness, depths=depths, surface=surface
@@ -191,8 +194,6 @@ def vertical_interp(
191
194
 
192
195
  # first a quick fix for the case when there is only one vertical depth
193
196
 
194
- if isinstance(levels, (int, float)):
195
- levels = [levels]
196
197
 
197
198
  # levels = [float(x) for x in levels]
198
199
 
@@ -1,5 +1,6 @@
1
1
  CITATION.cff
2
2
  CODE_OF_CONDUCT.md
3
+ CONTRIBUTIONS.md
3
4
  LICENSE
4
5
  MANIFEST.in
5
6
  README.md
@@ -21,7 +21,6 @@ Core abilities of nctoolkit include:
21
21
  - Calculating the correlations between variables
22
22
  - Calculating vertical statistics for the likes of oceanic data
23
23
  - Calculating ensemble statistics
24
- - Calculating phenological metrics
25
24
 
26
25
  Operation of the package requires the installation of Climate Data Operators (CDO). This is the computational backend for most of the methods used. No knowledge of CDO is required to use nctoolkit. A couple of methods provide users with the option of using netCDF Operators (NCO) as a backend. Guidance for how to install the backends are available `here <https://nctoolkit.readthedocs.io/en/stable/installing.html>`__.
27
26
 
@@ -47,7 +46,7 @@ extras_require["complete"] = ["geoviews", "rioxarray", "cfchecker", "geocube", "
47
46
  REQUIREMENTS = [i.strip() for i in open("requirements.txt").readlines()]
48
47
 
49
48
  setup(name='nctoolkit',
50
- version='1.1.10',
49
+ version='1.2.0',
51
50
  description=DESCRIPTION,
52
51
  description_content_type='text/plain',
53
52
  long_description=LONG_DESCRIPTION,
@@ -1,57 +0,0 @@
1
- import glob
2
- import os
3
-
4
- # function to find files in directory with a specified variable
5
-
6
-
7
- def create_ensemble(path="", recursive=True):
8
- """
9
- create_ensemble: Generate an ensemble of files from a directory.
10
-
11
- Parameters
12
- -------------
13
- path: str
14
- The directory to search for netCDF files
15
- recursive : boolean
16
- True/False depending on whether you want to search the path recursively.
17
- Defaults to True.
18
-
19
- Returns
20
- -------------
21
-
22
- files : list of files
23
-
24
- Examples
25
- ------------
26
-
27
- If you wanted to recursively find all netCDF files available in a directory "data", you would do this:
28
-
29
- >>> import nctoolkit as nc
30
- >>> nc.create_ensemble("data")
31
-
32
- If you wanted to find the files in that directory and ignore subdirectories, you would instead do this:
33
-
34
- >>> nc.create_ensemble("data", recursive = False)
35
-
36
-
37
- """
38
-
39
- # make sure the path exists
40
-
41
- if os.path.exists(path) is False:
42
- raise ValueError("The path provided does not exist!")
43
-
44
- # make sure the path ends with "/" if it is not empty
45
- if path != "":
46
- if path.endswith("/") is False:
47
- path = path + "/"
48
-
49
- if recursive:
50
- files = [f for f in glob.glob(path + "/**/*.nc*", recursive=True)]
51
- else:
52
- files = [f for f in glob.glob(path + "*.nc*")]
53
-
54
- if len(files) == 0:
55
- raise ValueError("There is no data in the target directory")
56
-
57
- return files
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes