6
$\begingroup$

I have a set of data consisting of daily measurements of two parameters. The data is available as a list of strings. Different parameters are measured each day. In the sample below, on January 1, a parameter "ab" has the value 100 and "c" has the value 101. (My data has undesirably split the names of the parameters wherever the name contains more than one word.)

lis = {"January","1","a","b","100","c","101","January","2","c","de","f","102","g","h",103"}

and would like to convert this to:

res ={{DateObject[{2020, 1, 1}],"ab","100","c","101"},{DateObject[{2020,1,2}],"cdef","102","gh","103"}}

In this data, there is always a day number following a month name. The parameter names are always preceded and followed by numbers and need to be combined into a single string element. Thank you for any suggestions.

$\endgroup$
2
  • 2
    $\begingroup$ Are you really really sure that you can no longer fix the original and prevent the splitting? This is a pretty horrible format, and a lot of work. Also, how come "de" did not get split? $\endgroup$
    – MarcoB
    Commented Jan 10, 2020 at 22:59
  • $\begingroup$ Also, what happened to e.g. January 10? Did it become {"January", "1", "0"}, or {"January", "10"}? $\endgroup$
    – MarcoB
    Commented Jan 10, 2020 at 23:08

3 Answers 3

10
$\begingroup$

Gosh, what a mess! Pretty much the only thing that could be easily recognized is the month names, so let's start from there to tokenize the list of strings.

I have added another example to your list, just to make sure that it works for more than two examples, and defined a list of months:

list = {"January", "1", "a", "b", "100", "c", "101", 
        "January", "2", "c", "de", "f", "102", "g", "h", "103", 
        "February", "20", "a", "b", "20"};

months = {"January", "February", "March", "April", 
          "May", "June", "July", "August", 
          "September", "October", "November", "December"};

Start by finding the month names and using them as delimiters to split the list:

Flatten@Position[list, _String?(MemberQ[months, #] &)]~
  Join~{Length[list] + 1};
Range[#1, #2 - 1] & @@@ Partition[%, 2, 1];
chopped = list[[#]] & /@ %

(* Out: 
{{"January", "1", "a", "b", "100", "c", "101"}, 
 {"January", "2", "c", "de", "f", "102", "g", "h", "103"}, 
 {"February", "20", "a", "b", "20"}} *)

The use an Interpreter to turn the first two strings, guaranteed to be month and day, to a DateObject:

dated = Cases[
    chopped,
    {month_, day_, rest__} :> {Interpreter["Date"][StringJoin[month, day]], rest}
 ]

(* Out: 
 {{DateObject[{2020, 1, 1}, "Day", "Gregorian", -6.`], "a", "b", "100", "c", "101"},
  {DateObject[{2020, 1, 2}, "Day", "Gregorian", -6.`], "c", "de", "f", "102", "g", "h", "103"}, 
  {DateObject[{2020, 2, 20}, "Day", "Gregorian", -6.`], "a", "b", "20"}} 
*)

Finally define a conversion function that uses ToExpression to convert each token, then decide whether the converted form is a string (i.e. a measurement name), or a number (i.e. a measurement value); in the latter case, it returns the number, otherwise it returns the string.

ClearAll[converter]
converter[s_String] :=
  StringCases[s,
    PatternSequence[
      name : Repeated[LetterCharacter],
      value : Repeated[DigitCharacter]
    ] :> Sequence[name, ToExpression[value]]
  ]

... and apply it to the previous result:

Cases[
  dated,
  {date_DateObject, strings__} :> {date, Sequence @@ converter@StringJoin[strings]}
]

(* Out: 
 {{DateObject[{2020, 1, 1}, "Day", "Gregorian", -6.`], "ab", 100, "c", 101}, 
  {DateObject[{2020, 1, 2}, "Day", "Gregorian", -6.`], "cdef", 102, "gh", 103}, 
  {DateObject[{2020, 2, 20}, "Day", "Gregorian", -6.`], "ab", 20}} 
*)
$\endgroup$
3
  • $\begingroup$ Yes it is a mess. But thanks for a start at it. $\endgroup$
    – Suite401
    Commented Jan 11, 2020 at 4:21
  • $\begingroup$ @Suite401 You are welcome. See my update answer: it should solve the whole problem. $\endgroup$
    – MarcoB
    Commented Jan 12, 2020 at 2:11
  • $\begingroup$ Thanks! a lot to study here. $\endgroup$
    – Suite401
    Commented Jan 12, 2020 at 6:24
4
$\begingroup$

A variation on Syed's approach:

f = {DateObject["2020," <> # <> #2],
     ## & @@ StringSplit[StringJoin @##3, x : NumberString :> x]} & @@@ 
    Split[#, Not @ StringMatchQ[#2, DatePattern[{"MonthName"}]] &] &;

f @ lis

enter image description here

$\endgroup$
3
$\begingroup$

Let me share a belated answer (as part of my learning process) using non-Sequence functions. The choice of the year used is arbitrary.

lis = {"January", "1", "a", "b", "100", "c", "101", "January", "2", 
  "c", "de", "f", "102", "g", "h", "103"};

months = {"January", "February", "March", "April", "May", "June", 
   "July", "August", "September", "October", "November", "December"};

Join[{
    DateObject[{2020, Det@Position[months, #[[1]]], 
      ToExpression@#[[2]]}, "Day"],
    Sequence @@ 
     StringJoin @@@ SplitBy[#[[3 ;;]], StringMatchQ[#, NumberString] &]
    }] & /@ Split[lis, FreeQ[months, #2] &]

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.