apmtools 0.2.31__tar.gz → 0.2.33__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: apmtools
3
- Version: 0.2.31
3
+ Version: 0.2.33
4
4
  Summary: A collection of tools for processing air pollution monitoring data
5
5
  Project-URL: Homepage, https://github.com/federlorenz/apmtools
6
6
  Author-email: Federico Lorenzetti <lorenzetti.federico@gmail.com>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "apmtools"
7
- version = "0.2.31"
7
+ version = "0.2.33"
8
8
  authors = [
9
9
  { name="Federico Lorenzetti", email="lorenzetti.federico@gmail.com" },
10
10
  ]
@@ -8,7 +8,7 @@ from io import StringIO
8
8
  from csv import writer
9
9
  from dateutil.parser import parse
10
10
 
11
- from .units import upas_units,lascar_units,purple_units
11
+ from .units import upas_units, lascar_units, purple_units, lcm_units
12
12
 
13
13
  class DictionaryPlus(dict):
14
14
  def __init__(self, *args, **kwargs):
@@ -451,11 +451,11 @@ class Dataset(DictionaryPlus):
451
451
 
452
452
  def scan_folder(self, directory="./", levels=[], level=0, monitor=None, levels_dict=None, gmt_timezone_shift=0, interpolate=False, add_m={}):
453
453
 
454
- from .data_processing import upas_processing, lascar_processing, purple_processing
454
+ from .data_processing import upas_processing, lascar_processing, purple_processing, lcm_processing
455
455
 
456
- if len(set(levels).intersection(set(["identifier", "start", "end", "length", "time", "pm25", "pm25correctedrh", "pm25correctedrhandgrav", "filterid", "grav_not", "grav"]))) > 0:
456
+ if len(set(levels).intersection(set(["identifier", "start", "end", "length", "time", "pm25", "pm25correctedrh", "pm25correctedrhandgrav", "filterid", "grav_not", "grav","PM2.5","PM10","C02","NO2"]))) > 0:
457
457
  print(
458
- f"the following levels names are not allowed: {set(levels).intersection(set(["identifier", "start", "end", "length", "time", "pm25", "pm25correctedrh", "pm25correctedrhandgrav", "filterid", "grav_not", "grav"]))}. Please choose different level names")
458
+ f"the following levels names are not allowed: {set(levels).intersection(set(["identifier", "start", "end", "length", "time", "pm25", "pm25correctedrh", "pm25correctedrhandgrav", "filterid", "grav_not", "grav","PM2.5","PM10","C02","NO2"]))}. Please choose different level names")
459
459
  return
460
460
  if levels_dict == None:
461
461
  levels_dict = dict(
@@ -473,6 +473,8 @@ class Dataset(DictionaryPlus):
473
473
  return "purple"
474
474
  case ['Time', 'CO(ppm)']:
475
475
  return "lascar"
476
+ case ['sensorName', 'deviceId']:
477
+ return "lcm"
476
478
  case _:
477
479
  return None
478
480
  except:
@@ -518,6 +520,21 @@ class Dataset(DictionaryPlus):
518
520
  except:
519
521
  print(
520
522
  f"processing of file {directory}{j} failed. Is this a {monitor} file?")
523
+ case "lcm":
524
+ try:
525
+ processed = lcm_processing(
526
+ directory, file=j, interpolate_data=interpolate)
527
+ for k, v in levels_dict.items():
528
+ if (v not in ["None", "_", "-", None]) and (k not in ["monitor", "", None, "-", "_"]):
529
+ processed.m[k] = v
530
+ processed.m["rejected"] = False
531
+ processed.m["filename"] = j
532
+ for key, value in add_m.items():
533
+ processed.m[key] = value
534
+ self[str(uuid.uuid4())] = processed
535
+ except:
536
+ print(
537
+ f"processing of file {directory}{j} failed. Is this a {monitor} file?")
521
538
  case "purple":
522
539
  try:
523
540
  processed = purple_processing(
@@ -559,6 +576,8 @@ class Dataset(DictionaryPlus):
559
576
  return ("lascar", run_lascar())
560
577
  elif x is Purple:
561
578
  return ("purple", run_purple())
579
+ elif x is Lcm:
580
+ return ("lcm", run_lcm())
562
581
  # elif x is Apm:
563
582
  # return "Apm"
564
583
  # elif x is PolarH10:
@@ -572,6 +591,8 @@ class Dataset(DictionaryPlus):
572
591
  return "Upas"
573
592
  case Lascar():
574
593
  return "Lascar"
594
+ case Lcm():
595
+ return "Lcm"
575
596
  case Purple():
576
597
  return "Purple"
577
598
  case Apm():
@@ -702,6 +723,58 @@ class Dataset(DictionaryPlus):
702
723
  out["time"] = pd.to_datetime(out["time"])
703
724
  return (Summary(out, metadata=set(columns2)))
704
725
 
726
+ def run_lcm():
727
+ a = 0.524
728
+ b = -0.0862
729
+ c = 5.75
730
+ d = self.subset(condition=lambda x: match_monitor(x) == "Lcm")
731
+ columns1 = ["identifier", "monitor",
732
+ "start", "end", "length", "time"]
733
+ columns2 = list(set(d.meta()))
734
+ columns3 = ["sensorName", "deviceId", "resolution",
735
+ "PM2.5", "PM10", "CO2", "NO2", "Temperature", "Humidity"]
736
+ columns4 = ["pm25correctedrh"]
737
+
738
+ buffer = StringIO()
739
+ with buffer as file:
740
+ df = writer(file, delimiter=",")
741
+ df.writerow(columns1+columns2+columns3+columns4)
742
+ count = 0
743
+ for k, v in d.items():
744
+ print(f"processing Lcm {count+1} out of {len(d)}")
745
+ v = v.dropna(subset="PM2.5")
746
+ identifier = k
747
+ start = v.start
748
+ end = v.end
749
+ length = v.length
750
+ for i in range(len(v)):
751
+ app = []
752
+ app.append(identifier)
753
+ app.append("lcm")
754
+ app.append(start)
755
+ app.append(end)
756
+ app.append(length)
757
+ app.append(v.index[i])
758
+ for m in columns2:
759
+ if m in v.m.keys():
760
+ app.append(v.m[m] if v.m[m] != None else "")
761
+ else:
762
+ app.append("")
763
+ for m in columns3:
764
+ app.append(v[m].iloc[i])
765
+ xx = (a*v["PM2.5"].iloc[i])+(b*v["Humidity"].iloc[i])+c
766
+ app.append(xx)
767
+ df.writerow(list(map(str, app)))
768
+ count += 1
769
+ file.seek(0)
770
+ out = pd.read_csv(file)
771
+ out["start"] = pd.to_datetime(out["start"])
772
+ out["end"] = pd.to_datetime(out["end"])
773
+ out["length"] = pd.to_timedelta(out["length"])
774
+ out["time"] = pd.to_datetime(out["time"])
775
+ return (Summary(out, metadata=set(columns2)))
776
+
777
+
705
778
  def run_purple():
706
779
  d = self.subset(condition=lambda x: match_monitor(x) == "Purple")
707
780
  columns1 = ["identifier", "monitor",
@@ -819,6 +892,8 @@ class Dataset(DictionaryPlus):
819
892
  return "Upas"
820
893
  case Lascar():
821
894
  return "Lascar"
895
+ case Lcm():
896
+ return "Lcm"
822
897
  case Purple():
823
898
  return "Purple"
824
899
  case Apm():
@@ -1454,9 +1529,26 @@ class Summary(pd.DataFrame):
1454
1529
  else:
1455
1530
  output = df
1456
1531
 
1457
- output = output[value_cols].agg(aggregate)
1532
+ if isinstance(frequency, str) and aggregate is None:
1533
+ if grouping_by != None:
1534
+ output = df[grouping_by+[frequency]]
1535
+ else:
1536
+ output = df[[frequency]]
1537
+ if isinstance(frequency, list) and aggregate is None:
1538
+ if grouping_by != None:
1539
+ output = df[grouping_by+frequency]
1540
+ else:
1541
+ output = df[frequency]
1542
+ if frequency == None and aggregate is None:
1543
+ if grouping_by != None:
1544
+ output = df[grouping_by]
1545
+ else:
1546
+ output = df
1547
+
1548
+ if aggregate is not None:
1549
+ output = output[value_cols].agg(aggregate)
1458
1550
 
1459
- if frequency != None or grouping_by != None:
1551
+ if aggregate != None and (frequency != None or grouping_by != None):
1460
1552
  output.columns = [
1461
1553
  "_".join(str(x) for x in col if pd.notna(x) and x != "")
1462
1554
  for col in output.columns.to_flat_index()
@@ -1472,8 +1564,8 @@ class Summary(pd.DataFrame):
1472
1564
  output.m["aggregate"] = aggregate
1473
1565
 
1474
1566
  return output
1475
-
1476
-
1567
+
1568
+
1477
1569
  def get_monitor(self, value):
1478
1570
 
1479
1571
  out = self.subset({"monitor": [value]})
@@ -1936,6 +2028,37 @@ class PurpleSeries(ApmSeries):
1936
2028
  def _constructor(self):
1937
2029
  return PurpleSeries
1938
2030
 
2031
+ class Lcm(Apm):
2032
+ monitor = "lcm"
2033
+
2034
+ def __init__(self, *args, **kwargs):
2035
+ Apm.__init__(self, *args, **kwargs)
2036
+ self.m = {}
2037
+ self.variable = "PM2.5"
2038
+
2039
+ @property
2040
+ def _constructor(self):
2041
+ return Lcm
2042
+
2043
+ @property
2044
+ def _constructor_sliced(self):
2045
+ return LcmSeries
2046
+
2047
+ @property
2048
+ def units(self):
2049
+ return lcm_units
2050
+
2051
+ class LcmSeries(ApmSeries):
2052
+ def __init__(self, *args, **kwargs):
2053
+ LcmSeries.__init__(self, *args, **kwargs)
2054
+ _metadata = ['m']
2055
+
2056
+ @property
2057
+ def _constructor(self):
2058
+ return LcmSeries
2059
+
2060
+
2061
+
1939
2062
  class PolarH10(dict):
1940
2063
  monitor = "polarh10"
1941
2064
  def __init__(self, *args, **kwargs):
@@ -4,7 +4,7 @@ import pandas as pd
4
4
  import numpy as np
5
5
  import datetime as dt
6
6
  import os as os
7
- from .classes import Apm, Sum, PolarH10, DictionaryPlus, Grav_Filter, Upas, Lascar, Purple
7
+ from .classes import Apm, Sum, PolarH10, DictionaryPlus, Grav_Filter, Upas, Lascar, Purple, Lcm
8
8
  from zipfile import ZipFile
9
9
  from copy import deepcopy
10
10
  from pandas.errors import EmptyDataError
@@ -841,6 +841,19 @@ def lascar_processing(directory, file, interpolation=1,interval="30 seconds", in
841
841
  out = Lascar(df)
842
842
  return out
843
843
 
844
+
845
+ def lcm_processing(directory, file, interpolation=1, interval="300 seconds", interpolate_data=True):
846
+ numeric = ["PM2.5", "PM10", "CO2", "NO2", "Temperature", "Humidity"]
847
+ dtformat = '%Y-%m-%dT%H:%M:%S'
848
+ df = pd.read_csv(directory+file, index_col="timestamp",
849
+ date_format={'Time': dtformat})
850
+ if interpolate_data:
851
+ df = interpolate(df, 300, interpolation, pd.Timedelta(
852
+ '00:06:00'), numeric_columns=numeric, add_binary_counter=False)
853
+ df = keep_interval(df, interval)
854
+ out = Lcm(df)
855
+ return out
856
+
844
857
  def sum_interpolation(file, interpolation=1, interval="5 minutes", timing=False):
845
858
  if timing:
846
859
  start = time.process_time()