Jump to content

Thorsten Vogt

Super Seeqer
  • Posts

    171
  • Joined

  • Last visited

  • Days Won

    53

Posts posted by Thorsten Vogt

  1. Hi Esther,

    based on the information you provided I would create a condition for each of the two durations you want to calculate. To do this in formula I first recommend you to rename the variables for better readability of the formula.

    For the first one ("ADD Durations for when equipment is stopped and in lag OR when the equipment is stopped or standby") you may use:

     ($stopped && $lag) || ($stopped || $standby) //-> can be shortened to $stopped || $standby

    For the second one ("ADD Durations for when equipment is stopped but not in lag or standby") try:

    $stopped && !($lag || $standby)

    image.thumb.png.d0ff9fa28df22938a2ea0d5da9db6837.png

    Depending on what you want to do with the duration you may use different tools for calculation (e.g. Signal from Condition, Scorecard Metric, use of  capsule properties, ...)

    Let me know if you need more help.

    Regards,

    Thorsten

    • Thanks 2
  2. Hi Isaac,

    you may try this instead:

    Max:

    $days = days()
    $aMax = $a.aggregate(maxValue(), $days, durationKey())
    $bMax = $b.aggregate(maxValue(), $days, durationKey())
    $gMax = $g.aggregate(maxValue(), $days, durationKey())
    $hMax = $h.aggregate(maxValue(), $days, durationKey())
    
    $aIsMax = $aMax.isGreaterThan($bMax).intersect($aMax.isGreaterThan($gMax)).intersect($aMax.isGreaterThan($hMax))
    $bIsMax = $bMax.isGreaterThan($aMax).intersect($bMax.isGreaterThan($gMax)).intersect($bMax.isGreaterThan($hMax))
    $gIsMax = $gMax.isGreaterThan($aMax).intersect($gMax.isGreaterThan($bMax)).intersect($gMax.isGreaterThan($hMax))
    $hIsMax = $hMax.isGreaterThan($aMax).intersect($hMax.isGreaterThan($bMax)).intersect($hMax.isGreaterThan($gMax))
    
    "".toSignal(1d)
      .splice("Area A".toSignal(1d), $aIsMax)
      .splice("Area B".toSignal(1d), $bIsMax)
      .splice("Area G".toSignal(1d), $gIsMax)
      .splice("Area H".toSignal(1d), $hIsMax)

    Min:

    $days = days()
    $aMin = $a.aggregate(minValue(), $days, durationKey())
    $bMin = $b.aggregate(minValue(), $days, durationKey())
    $gMin = $g.aggregate(minValue(), $days, durationKey())
    $hMin = $h.aggregate(minValue(), $days, durationKey())
    
    $aIsMin = $aMin.isLessThan($bMin).intersect($aMin.isLessThan($gMin)).intersect($aMin.isLessThan($hMin))
    $bIsMin = $bMin.isLessThan($aMin).intersect($bMin.isLessThan($gMin)).intersect($bMin.isLessThan($hMin))
    $gIsMin = $gMin.isLessThan($aMin).intersect($gMin.isLessThan($bMin)).intersect($gMin.isLessThan($hMin))
    $hIsMin = $hMin.isLessThan($aMin).intersect($hMin.isLessThan($bMin)).intersect($hMin.isLessThan($gMin))
    
    "".toSignal(1d)
      .splice("Area A".toSignal(1d), $aIsMin)
      .splice("Area B".toSignal(1d), $bIsMin)
      .splice("Area G".toSignal(1d), $gIsMin)
      .splice("Area H".toSignal(1d), $hIsMin)

    Regards,

    Thorsten

  3. Hi Isaac,

    don't know why I did not came across this solution first, which is fairly the easiest to achive I think:

    You can use boolean operators for this.

    Determine Max Signal:

    $days = days()
    
    //Calculate maximum per day
    $aMax = $a.aggregate(maxValue(), $days, durationKey())
    $bMax = $b.aggregate(maxValue(), $days, durationKey())
    $gMax = $g.aggregate(maxValue(), $days, durationKey())
    $hMax = $h.aggregate(maxValue(), $days, durationKey())
    
    //Which is max?
    $aIsMax = $aMax > $bMax && $aMax > $gMax && $aMax > $hMax
    $bIsMax = $bMax > $aMax && $bMax > $gMax && $bMax > $hMax
    $gIsMax = $gMax > $aMax && $gMax > $bMax && $gMax > $hMax
    $hIsMax = $hMax > $bMax && $hMax > $gMax && $hMax > $gMax
    
    //Generate signal
    "".toSignal(1d)
      .splice("Area A".toSignal(1d), $aIsMax)
      .splice("Area B".toSignal(1d), $bIsMax)
      .splice("Area G".toSignal(1d), $gIsMax)
      .splice("Area H".toSignal(1d), $hIsMax)

    Determine Min Signal:

    $days = days()
    
    //Caulate minimum per day
    $aMin = $a.aggregate(minValue(), $days, durationKey())
    $bMin = $b.aggregate(minValue(), $days, durationKey())
    $gMin = $g.aggregate(minValue(), $days, durationKey())
    $hMin = $h.aggregate(minValue(), $days, durationKey())
    
    //Which is min?
    $aIsMin = $aMin < $bMin && $aMin < $gMin && $aMin < $hMin
    $bIsMin = $bMin < $aMin && $bMin < $gMin && $bMin < $hMin
    $gIsMin = $gMin < $aMin && $gMin < $bMin && $gMin < $hMin
    $hIsMin = $hMin < $bMin && $hMin < $gMin && $hMin < $gMin
    
    //Generate Signal 
    "".toSignal(1d)
      .splice("Area A".toSignal(1d), $aIsMin)
      .splice("Area B".toSignal(1d), $bIsMin)
      .splice("Area G".toSignal(1d), $gIsMin)
      .splice("Area H".toSignal(1d), $hIsMin)

     Regards,

    Thorsten

  4. Hi Pablo,

    you can use transform() and replace() to do this. I made an example with a signal that contains the following data:
    image.thumb.png.d5ec3642f3d1b1e3b9ae585685f5e0c3.png

    To create a signal that contains only the numeric values I used the following formula:

    $originalSignal.transform(($p, $c) -> sample($c.getKey(), $c.getValue().replace('/\\w+\\s(\\d+)/', '$1')))

    Transform is used to access every sample in the signal by specifying a lambda expression. The current sample is temporarily stored in the variable $c. $p contains the previous sample which is not used here. For each sample of the original signal a new sample is created by using the timestamp (key) of the original sample. The value for the new sample is determined by the value of the sample of the original signal on which replace() is executed. As the name suggests replace() is used to replace portions of a string. In this case I make use of a regular expression to determine the numeric value inside the original string and replace the original string by the determined value. The result looks like this:

    image.thumb.png.5f5b80238bb4aafcdccb848c5bb0726e.png

    For your example (12345_20200326_PJR) you have to modify the replace part to:

    .replace('/(\\d+)_\\d+_\\w+/', '$1')

    Hope this helps.

    Regards,

    Thorsten

     

    • Like 3
    • Thanks 1
  5. On 2/6/2020 at 1:59 AM, Bryan said:

    Hey Kristopher,

    Thanks for the reply to this post. Really appreciate it. These formula are definitely useful but in terms of scaling to 100+ grades & each grade operates with different conditions, it posts a challenge to replicate the formula.

    I am looking for something more automated and require less manual input, in terms of, calculating the standard deviation based on grade for that condition and use it as boundaries. 

    Hi Bryan,

    you can do this by using Seeq Data Labs.

    I prepared an example based on the data @Kristopher Wiggins used.

    image.thumb.png.7dff31a39d26de6002a9eaf17f5fbbc3.png

    The following code creates a boundary signal (in this case the mean), which can be used for further calculations:

    import pandas as pd
    
    #search signals
    signals = spy.search(pd.DataFrame([{"Name": "/(Product_Viscosity|Grade_Code_String)/"}]), 
                         workbook="4218D647-C7CB-4062-A6CF-D114F2E5E559")
    
    #get data for last 60 days
    data = spy.pull(signals, grid= None, start = pd.datetime.utcnow() - pd.Timedelta("60d"))
    
    #calculate mean
    means = data.groupby('Grade_Code_String').mean()
    
    #create boundary signal and push back to Seeq
    calc_dict = dict()
    calc_dict['Name'] = 'BoundarySignal'
    calc_dict['Type'] = 'Signal'
    formula = "0.tosignal(5min)"
    
    for m in means.itertuples():
        formula = formula + ".splice(" + str(m.Product_Viscosity) + ".toSignal(5min), $s == '" + m.Index + "')"
    
    calc_dict['Formula'] = formula
    calc_dict['Formula Parameters'] = {
        '$s': signals[signals["Name"] == 'Grade_Code_String']
    }
    
    metadata_for_calcs = list()
    metadata_for_calcs.append(calc_dict)
    
    spy.push(metadata=pd.DataFrame(metadata_for_calcs), workbook="4218D647-C7CB-4062-A6CF-D114F2E5E559")

    image.thumb.png.6938c1c70a6a28f96d06c9306330b38d.png

    Hope this helps.

    Regards,

    Thorsten

  6. 22 minutes ago, Mark Derbecker said:

    With respect to archiving, I think if you change `Archived` to `True` in the DataFrame and then `spy.push()` that item, it should mark it as Archived. Have you tried that?

    Note that the `spy.push(archive)` argument is a little misleading-- it is used for another purpose. I'll think about whether to rename that for clarity.

    Hello Mark,

    like this way?
    image.thumb.png.96497ba0830fa538d79cb7c8d419d384.png

    The body of "Push Result" is: '{"statusMessage":"Invalid unit \\u0027/Min\\u0027"}'

    Regards,

    Thorsten

  7. Hi,

    I came across this issue when I used Seeq Data Labs for uploading data from an Excel file into Seeq. The file contained some signals, that used "1/Min" as UOM. While uploading the data to Seeq I got the message that the Unit is not supported. I created a sample with one signal to reproduce the issue:

    image.thumb.png.d43bda5b814c2e53a77c3bd38a349924.png

     

    Is there a way to let SDL check for invalid units before pushing data back to Seeq? The bad thing is that the signal is created in Seeq and cannot be archived within SDL or Seeq Workbench:

    image.thumb.png.47b25c2b60601a23935986676b58a39c.png

    image.thumb.png.d39b6d512710df65abba72912f572577.png

    Only way to archive these items seems to be using SDK or REST API.

    Regards,

    Thorsten

     

     

     

  8. Hi Kathryn,

    yes, there are multiple options.

    You can use aggregate():

    image.png.b2deac09fcdfb026285f627d96e0aef2.png

    This will give you a stepped signal containing the number of capsules per week. 

     

    image.thumb.png.270fc2cc7d398bccf97f27358c90149d.png

    Another option would be using the Histogram tool:

    image.thumb.png.71506bcd0f08095684b809d0e3389f04.png

    Or you may use a scorecard metric. When using type "Simple" it will calculate the values for the current display range.

    image.thumb.png.f0bc1f754a60fc9d5fa1277092421258.png

     

    Hope this helps.

    Regards,

    Thorsten

    • Like 1
  9. Hi,

    when pushing data to Seeq there is an option to attach metadata for specifying some additional properties. According to the documentation specifying "Unit of Measure" as a metadata property would enable to set the unit of measure inside Seeq server. However the property has to be named "Value Unit of Measure" otherwise Seeq server will not display the UOM in trend view.

    Regards,

    Thorsten

  10. Hi,

    when I try to get data into Jupyter Notebooks by using spy.pull(), I receive no results when I omit "start" and "end" parameters. According to the documentation it should return values for the last hour. However if I specify the timestamps in UTC spy returns the desired values:

    image.png.b1b57ee30a5b18de4bece0d8b6d39f29.png

    Is there a way to get this working without specifying the timestamps?

    Regards,

    Thorsten

  11. Hi,

    treefile connector seems to have problems when using a regex quantifier like "\d{4,5}" inside CSV file. 

    Error message:
    value not one of declared Enum instance names: [Signal, Data, Condition, Scalar, Asset]

    I think that the "," inside the quantifier is responsible for this and the columns for the evaluation of the configuration line are misinterpreted. I was able to work around this by using "\d\d\d\d\d?". Is there a way to specify another delimiter for the treefile connector to use when working with CSV files?

    Regards, 

    Thorsten

  12. Hi Robin,

    maybe you want to try this. For this demo I created 3 signals based on the example data of Seeq as I did not have data like yours. For each of the signals I created capsules whenever the value is above 1kW:

    image.thumb.png.7501714d2e8f4a0617e62aa574bba23c.png

    In the next step I joined the running conditions to one parent condition:

    image.thumb.png.d76b4f54ca7b9f3d5ed2aa8bdf407080.png

    Now I am able to calculate the delay between the start of the "All running" capsules and the "Running" capsules of each signal and delay the original signal by this value:

    image.png.b26e3d4b3630377251d56d10f19ce5a5.png

    image.thumb.png.cfb662e8f71d120340f422bdee6ec33b.png

    In the last step I created capsules for the delayed signal, whenever the value is about 1kW:

    image.thumb.png.09252d69989363e29e9f84b727699a91.png

    You may have to do some adjustments to this example regarding your needs. Hope this helps.

    Regards,

    Thorsten

     

    • Like 2
  13. Hi Ali,

    startKey(), middleKey() and endKey() provide just the timestamp where Seeq will put the calculated value. In your example using periods(1year,6month) Seeq will always calculate the average for one year starting from January (e.g. 01/01/2018 - 01/01/2019) and the average starting from July (e.g. 07/01/2018 - 07/01/2019).

    For the period 01/01/2018 - 01/01/2019 using

    - startKey() will put the value to 01/01/2018
    - middleKey() will put the value to 07/01/2018
    - endKey() will put the value to 01/01/2019

    The marked columns in the following Analysis all have the same value for the period of last year :

     

    image.thumb.png.bf74028e0adbdaf2e8f7acf1c28a8b82.png

    Hope this helps.

    Regards,

    Thorsten

     

    • Like 2
  14. Hi,

    I configured Seeq to use data from a SQL Server database. However, after rebooting the machine hosting SQL Server or restarting SQL Server service the connection in Seeq is not reestablished. Error message:

    Error getting data: The target datasource is disconnected or no longer exists

    I have to restart Seeq service to get this resolved. Is this a known issue and is there a way to get around this?

    Regards, 

    Thorsten

  15. Hi Ali,

    in addition to @Sanman Mehta's answer if you need just the difference between the values:  

    You can use runningDelta() for getting the change of the value from sample to sample or use Signal From Condition and apply "Delta" as summary statistic. That will give you the difference between values at end and start of the capsule.


    Regards,

    Thorsten

    • Like 2
  16. Hi Chris,

    you can use Deviation Search for this. 

    I altered @Lindsey.Wilcox example by using another signal that acts as the limit:image.thumb.png.5fa0ef2110f534963dcb2b5b0e2695de.png

    Using Deviation Search you can find capsules, when the value is above the threshold:

    image.thumb.png.dfdad882131c63b65ebf28c45d5ecbb6.png

    In the last step you combine the conditions to get the desired capsules. Make sure you uncheck "Inclusive of B", otherwise the resulting capsules are extended by the duration of B.

    image.png.9e3c7023156914c63e24462e6ddebd0f.png

    image.thumb.png.236b4a273f91b02d71a4474bd924969d.png

    Hope this helps.

    Regards,

    Thorsten

     

     

    • Like 1
×
×
  • Create New...