Message Boards Message Boards

0
|
10971 Views
|
8 Replies
|
2 Total Likes
View groups...
Share
Share this post:

Simple Unit Conversions

Posted 10 years ago

I continue to find functional programming somewhat confusing.

I have lists of depths with unit, with differing units drawn from columns of a database. I wish to simply convert these lists of depths so that all elements of the list are, with each element given in meters. Ignoring non-essential aspects, I simplify the problem below.

As an example input, I use the following list:

result[All,13]] = {"50 ft", "8 ft", "16 m", "35 ft", "70 ft", "6 m", "60 ft", "90 ft", "8 ft"}

Although I naively thought that ft and m are interpretable by Quantity[result[[All,13]]], I get an "Unable to interpret unit specification ..."

Consequently, I substitute the explicit values for units and then have tried to execute a slightly overloaded function to do this:

result[[All, 13]] = StringReplace[result[[All, 13]], {"fm" -> "Fathoms", "ft" -> "Feet", "m" -> "Meters"}]

depthfun[n_ /; n == ""] := Module[{y}, y = n;];
depthfun[n_ /; n != ""] := 
  Module[{x, y1, y2, z}, x = StringSplit[n]; y1 = x[[1]]; 
   y2 = x[[2]] // InputForm;
   If[y2 == "Feet", z = UnitConvert[Quantity[y1, y2], "Meters"], 
    If[y2 == "Fathoms", z = UnitConvert[Quantity[y1, y2], "Meters"], 
     z = Quantity[y1, y2]]]];

which I then attempt to Map across the list:

Map[Apply[depthfun[#] &], result[[All, 13]]]

Unfortunately, this doesn't work and simply leaves the original list unchanged. I don't understand why. Can anyone help me figure out why my function fails to perform the unit conversions? No doubt, there is a more appropriate/succinct way to do this, so alternative approaches would be instructive.

Thank you.

POSTED BY: Stuart Poss
8 Replies
Posted 10 years ago

Thank you for taking the time to provide further explanation.

Your remarks clarify some of my confusion.

For example the List

s = {"a", "b", c, 5}

which only has one level, level 1, with the Head itself at level 0.

has the generalized Head of List so that Head[s] returns "List".

If what you say "replacing of all the heads with a different head", one might expect that the following

ToString @@@ s

would make the Heads Head[s[[1]]], Head[s[[2]]], Head[s[[3]]], Head[s[[4]]] all return "String".

However, in fact this seems only true for Head[s[[1]] and Head[s[[2]], which are already strings, whereas Head[s[3]]] returns Symbol and Head[s[[4]]] returns Integer. It would seem that although all elements of List s taken together have a Head of String after the Apply at Level 1 function syntax is executed, each element within the list still retains its "atomic" [correct word?] character.

Thus, it seems that list s of the fundamental types is not, subsequent to the application of ToString @@@ s, a list of all elements with a Head of String, but still a list of mixed types [atoms?]. However, this behavior seems confined to when the expressions making up the elements of a list are atoms rather than functions.

However, in going through your example of Apply[f, {g[a], g[b], g[c]}, 1], I observe that as you and the documentation indicate if we assign this to a list of functions, then Apply works exactly as you say. I can see I must think more carefully about what elements are in my list, expressions that may be atoms or expressions that may be functions.

Consequently, I think much of my confusion is that often the individual elements of my lists are of mixed type and hence iterating or mapping over them can often produce unexpected results when the functions I create are not properly overloaded and where I fail to appreciate the difference between a list of "atoms" and a list of functions serving as input.

As for your comment with respect to Timing, your approach seems about 4 times more efficient, if viewed this way:

In[92]:=  Do[f @@@ {g[a], g[b], g[c]}, 100000] // Timing

{0.109375, Null}

Do[Map[Apply[f[#] &], {g[a],g[b], g[c]}], 100000] // Timing 

{0.46875, Null}

and 11 times more efficient, if set up this way:

u = Table[g[a], {a 1, 100000}]; v = f @@@ {u}; // Timing {0.015625, Null}

or this way,

y = Map[Apply[f[#]&, u] //Timing {0.171875, Null}

presumably because of the cost of rebuilding the pure function each time in the list or processing them sequentially. This is something worth keeping in mind for long lists or for lists that are repeatedly iterated in some way.

So your suggestion seems not only to be cleaner but also more efficient.

POSTED BY: Stuart Poss

I'll try to explain the point about syntax. It is rather subtle, and I myself took some time before I understood it, back when I was learning the language. Your syntax

Map[Apply[depthfun[#] &], Take[depthsReal3, 10]]

contains Take[depthsReal3, 10], which expands to a list of this structure:

{Quantity[n1,u], Quantity[n2,u],...}

What you want is to turn this list into this other form:

{depthfun[n1,u], depthfun[n2,u],...}

that is, you want to replace the Quantity heads of the elements of the list with the symbol depthfun (and then evaluate). This "replacing of all the heads with a different head" is precisely what Apply is designed to do. In your case you want to replace all the heads that are at level 1:

Apply[depthfun, {Quantity[n1,u], Quantity[n2,u],...}, 1]

a construct which is needed often enough to deserve a cryptic shorthand

depthfun@@@{Quantity[n1,u], Quantity[n2,u],...}

Your method with Map achieves the same result, but with a more convoluted syntax. I don't know if there is a speed penalty involved, probably negligible.

POSTED BY: Gianluca Gorni
Posted 10 years ago

Thank you for the followup. It's much appreciated.

In the following I attempt to implement the suggestions and compare them (little involved since my original function was a list of strings and not a list of quantities), attempting to obtain some timing comparisons. Although not all comparisons could be carried out, it appears that several issues are worth noting.

1) with respect to evaluating depths that are integers vs. depths that are reals, the efficiency of the unit conversion doesn't seem to be affected.. However, there seems to be considerable differences in run times for different sets of runs, presumably associated with Network latency, which is generally as large relative to actual computation times.

Thus, Dana's suggestion works nicely for this, when the length of the list is small, but takes a very wait time long time (about 5 minutes for 1000 entries even though I am running on a Comcast 150MB/sec Network, possibly due to Network errors + latency. The actual computation time is about 21.7 sec to pass reals vs 18.9 sec to pass integers]. Maybe just bad (noisy) service (I pay a fortune, mostly to watch commercials)?

2) using the following code (see below), I am unable to get Gianluca's alternative approach using switch to work and output seem to remain unevaluated. I must be missing something here.

3) Network errors can make the conversions take a long time and consequently, I could not get Sean's approach to work, because of network timeout errors, except for the shortest of lists.

4) I don't quite understand what Gianluca was trying to tell me with respect to syntax, although it's been evident for some time that I don't fully appreciate how to use Apply. Perhaps this is because I don't understand what the {g[a], g[b], g[c]} is meant to represent in this particular situation.

Using the variables described below, if I only use the Apply function to the first 10 entries,

In[15]:= depthfun @@@ Take[depthsReal3, 10]  (* where do the "{g[a], etc} go/mean here}" ? *)

Out[15]= {"81.9173 feet", "42.874 feet", "91.2999 feet", "4.78915 \
feet", "92.2145 feet", "68.4963 feet", "75.1933 feet", "12.4573 \
feet", "9.4775 feet", "85.2124 feet"}

I only see the unconverted output (still in feet, like I would expect using Apply at level 1), whereas if I Map depthfun (as modified by Gianluca's suggestion)

depthfun /@ Take[depthsReal3, 10]

I get the conversion for the first 10 random real entries of converted quantities as expected:

{Quantity[24.9684, "Meters"], Quantity[13.068, "Meters"], Quantity[27.8282, "Meters"], Quantity[1.45973, "Meters"], Quantity[28.107, "Meters"], Quantity[20.8777, "Meters"], Quantity[22.9189, "Meters"], Quantity[3.79699, "Meters"], Quantity[2.88874, "Meters"], Quantity[25.9727, "Meters"]}

What follows is a short notebook I used to benefit further from the suggestions kindly offered. Thanks again for the comments. I just might figure out how to do functional programming yet.

Set Seed and Generate 1000 Random Depths

In[2]:= SeedRandom[1234];

In[3]:= depthsReal = Table[ToString[Quantity[ RandomReal[{0, 1000}], "ft"]], 1000];

In[4]:= Take[depthsReal, 10]

Out[4]= {"876.608 feet", "521.964 feet", "86.2234 feet", "377.913 feet", "11.6446 \
feet", "927.266 feet", "543.757 feet", "479.332 feet", "245.349 feet", \
"759.896 feet"}

In[5]:= depthsInteger = 
  Table[ToString[Quantity[ RandomInteger[{0, 1000}], "ft"]], 1000];

In[6]:= Take[depthsInteger, 10]

Out[6]= {"67 feet", "111 feet", "909 feet", "355 feet", "273 feet", "454 feet", "857 \
feet", "370 feet", "234 feet", "335 feet"}

In[7]:= Quiet[(UnitConvert[#1, "Meter"] & ) /@ depthsReal; // Timing]

Out[7]= {21.7188, Null}

In[8]:= Quiet[(UnitConvert[#1, "Meter"] &) /@ depthsInteger; // Timing]

Out[8]= {18.8906, Null}

Place random depths into a list of Strings rather than a list of Quantities as I originally had:

In[9]:= depthsReal2 = 
  StringSplit[Table[ToString[Quantity[ RandomReal[{0, 100}], "ft"]], 1000]];

In[10]:= depthsInteger2 = 
  StringSplit[
   Table[ToString[Quantity[ RandomInteger[{0, 100}], "ft"]], 1000]];

In[11]:= depthsInteger3 = 
  Table[depthsInteger2[[i, 1]] <> " " <> depthsInteger2[[i, 2]], {i, 1, 
    1000}];

In[12]:= depthsReal3 = 
  Table[depthsReal2[[i, 1]] <> " " <> depthsReal2[[i, 2]], {i, 1, 1000}];

In[13]:= (* alternate approach suggested by Gianluca *)

In[14]:= StringCases[
  Take[depthsReal3, 5], (n : NumberString) ~~ (Whitespace ..) ~~ unit__ :> 
   UnitConvert[
    Quantity[N@ToExpression[n], 
     Evaluate@Switch[unit, "m", "Meters", "ft", "Feet", "fm", "Fathoms"]], 
    "Meters"]] // InputForm

Out[14]//InputForm=
{{UnitConvert[Quantity[49.4188, Switch["feet", "m", "Meters", "ft", "Feet", 
     "fm", "Fathoms"]], "Meters"]}, 
 {UnitConvert[Quantity[7.21171, Switch["feet", "m", "Meters", "ft", "Feet", 
     "fm", "Fathoms"]], "Meters"]}, 
 {UnitConvert[Quantity[56.4269, Switch["feet", "m", "Meters", "ft", "Feet", 
     "fm", "Fathoms"]], "Meters"]}, 
 {UnitConvert[Quantity[14.0274, Switch["feet", "m", "Meters", "ft", "Feet", 
     "fm", "Fathoms"]], "Meters"]}, 
 {UnitConvert[Quantity[40.3302, Switch["feet", "m", "Meters", "ft", "Feet", 
     "fm", "Fathoms"]], "Meters"]}}

In[15]:= (*Interpreter["Quantity"][depthsReal3];*)

In[16]:=
(*Interpreter["Quantity"][{"567 feet", "483 feet", "151 feet", "261 feet", "364 feet", "30 feet", 
 "590 feet", "213 feet", "569 feet", "925 feet"}];*)

In[17]:= (* depthfun rewritten As Suggested by Gianluca *)

In[18]:= depthfun[""] := Null;
depthfun[n_ /; n != ""] := Module[{y1, y2}, {y1, y2} = StringSplit[n];
   y1 = N@ToExpression[y1];
   UnitConvert[Quantity[y1, y2], "Meters"]];

In[20]:= depthfun /@ Take[depthsReal3, 10]

Out[20]= {Quantity[15.0629, "Meters"], Quantity[2.19813, "Meters"], 
 Quantity[17.1989, "Meters"], Quantity[4.27555, "Meters"], 
 Quantity[12.2926, "Meters"], Quantity[29.6987, "Meters"], 
 Quantity[3.15828, "Meters"], Quantity[20.6579, "Meters"], 
 Quantity[18.9287, "Meters"], Quantity[12.0336, "Meters"]}

In[21]:= depthfun @@@ Take[depthsReal3, 10]

Out[21]= {"49.4188 feet", "7.21171 feet", "56.4269 feet", "14.0274 feet", "40.3302 \
feet", "97.4367 feet", "10.3618 feet", "67.7754 feet", "62.102 feet", \
"39.4802 feet"}

In[22]:= Timing[depthfun /@ Take[depthsReal3, 1000];]

Out[22]= {0.78125, Null}

In[25]:= Timing[depthfun /@ Take[depthsInteger3, 10]]

Out[25]= {0.015625, {Quantity[7.9248, "Meters"], Quantity[27.432, "Meters"], 
  Quantity[30.1752, "Meters"], Quantity[24.9936, "Meters"], 
  Quantity[24.0792, "Meters"], Quantity[4.2672, "Meters"], 
  Quantity[18.8976, "Meters"], Quantity[28.6512, "Meters"], 
  Quantity[9.144, "Meters"], Quantity[21.0312, "Meters"]}}

(* Don't quite understand when the following construction suggested to me by Wolfram help works and when it doesn't; \
not working here*)

In[24]:= Map[Apply[depthfun[#] &], Take[depthsReal3, 10]]

Out[24]= {"49.4188 feet", "7.21171 feet", "56.4269 feet", "14.0274 feet", "40.3302 \
feet", "97.4367 feet", "10.3618 feet", "67.7754 feet", "62.102 feet", \
"39.4802 feet"}
POSTED BY: Stuart Poss

This is probably the most straightforward solution for converting things into units:

Interpreter["Quantity"][{"50 ft", "8 ft", "16 m", "35 ft", "70 ft",  "6 m", "60 ft", "90 ft", "8 ft"}]
POSTED BY: Sean Clarke
Posted 10 years ago

This worked for me using version 10.2:

lst = {"50 ft", "8 ft", "16 m", "35 ft", "70 ft", "6 m", "60 ft", "90 ft", "8 ft"};

(UnitConvert[#1, "Meter"] & ) /@ lst

{381/25m,1524/625m,16m,2667/250m,2667/125m,6m,2286/125m,3429/125m,1524/625m}
POSTED BY: Dana DeLouis

You can convert a Quantity to a string simply with ToString[Quantity[2,"Meters"]]. Your construct Map[Apply[depthfun[#] &], result[[All, 13]]] is better done using the syntax

Apply[f, {g[a], g[b], g[c]}, 1]

(Apply at level 1) or with the shorthand

f @@@ {g[a], g[b], g[c]}

instead of mapping the Apply

Map[Apply[f[#] &], {g[a], g[b], g[c]}]
POSTED BY: Gianluca Gorni
Posted 10 years ago

This answers my question quite nicely.

Thank you for introducing me to the Switch function Gianluca. Both work and are about equally fast. However, for my purposes it seems the first is more desirable as it returns a string, rather than Quantity objects. Also, it seems easier to control the precision, This can be done simply in the first by a slight modification:

y1 = SetPrecision[N@ToExpression[y1], 3];

whereas I'm still trying to figure out how to do so in the latter.

POSTED BY: Stuart Poss

You must convert your variable y1 from a string to an expression, and you should not introduce InputForm into calculations, but only for final display

depthfun[""] := Null;
depthfun[n_ /; n != ""] := Module[{y1, y2},
   {y1, y2} = StringSplit[n];
   y1 = N@ToExpression[y1];
   UnitConvert[Quantity[y1, y2], "Meters"]];

Another way to do the same task, with StringCases:

dpths = {"50 ft", "8 ft", "16 m", "35 ft", "70 ft", "6 m", "60 ft", 
   "90 ft", "8 ft"};
StringCases[
  dpths, (n : NumberString) ~~ (Whitespace ..) ~~ unit__ :> 
   UnitConvert[
    Quantity[N@ToExpression[n], 
     Evaluate@
      Switch[unit, "m", "Meters", "ft", "Feet", "fm", "Fathoms"]],
    "Meters"]] // InputForm
POSTED BY: Gianluca Gorni
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract