8
$\begingroup$

I want to a apply a function to the 2nd row and 2nd column of an association:

dt =
  <|"Arizona" -> <|"1801" -> 60, "1802" -> 1530, "1803" -> 100, "1804" -> 665|>,
   "Kansas" -> <|"1801" -> 1160, "1802" -> 415, "1803" -> 825, "1804" -> 230|>,
   "Texas" -> <|"1801" -> 365, "1802" -> 135, "1803" -> 70, "1804" -> 1280|>,
   "Vermont" -> <|"1801" -> 60, "1802" -> 485, "1803" -> "100", "1804" -> 555|>|>;

I tried:

Query[
   <|#, "Kansas" -> f /@ #Kansas|> &,
   <|#, "1802" -> f @ #"1802"|> &] @ dt // Dataset[#, ItemSize -> 7] &

and got: enter image description here

At "Kansas" / "1802" the function was applied twice, but I want it to be applied only once. Is there an easy / elegant way to achieve this?

$\endgroup$
1
  • $\begingroup$ Sorry, @kglr, changed it $\endgroup$
    – eldo
    Commented Jul 29, 2023 at 17:41

5 Answers 5

11
$\begingroup$

If we change dt to:

dt = Dataset@<|
    "Arizona" -> <|"1801" -> 60, "1802" -> 1530, "1803" -> 100, 
      "1804" -> 665|>, 
    "Kansas" -> <|"1801" -> 1160, "1802" -> 415, "1803" -> 825, 
      "1804" -> 230|>, 
    "Texas" -> <|"1801" -> 365, "1802" -> 135, "1803" -> 70, 
      "1804" -> 1280|>, 
    "Vermont" -> <|"1801" -> 60, "1802" -> 485, "1803" -> "100", 
      "1804" -> 555|>|>;

Perhaps this:

Block[{f},
 SetAttributes[f, Flat];
 dt[MapAt[Map@f, "Kansas"], MapAt[f, "1802"]]
 ]

(If -f[..] in the 1802 column, then dt[MapAt[Map@f, "Kansas"]][All, MapAt[-f[#] &, "1802"]] for the 3rd line.)

$\endgroup$
6
$\begingroup$

Maybe not elegant, but at least bundles it up into something a relatively understandable and generalizable function:

CrossApply[a_, b_, f_] :=
  KeyValueMap[
    If[
      #1 == a,
      a -> f /@ #2,
      #1 -> ReplacePart[#2, b -> f[#2[b]]]] &]

Usage:

CrossApply["Kansas", "1802", f][dt]
(* {Arizona-><|1801->60,1802->f[1530],1803->100,1804->665|>,
    Kansas-><|1801->f[1160],1802->f[415],1803->f[825],1804->f[230]|>,
    Texas-><|1801->365,1802->f[135],1803->70,1804->1280|>,
    Vermont-><|1801->60,1802->f[485],1803->100,1804->555|>} *)
$\endgroup$
6
$\begingroup$

1.

MapIndexed[If[FreeQ["Kansas" | "1802"] @ #2, #, h @ #] &, Dataset@dt, {2}]

enter image description here

2.

MapAt[Replace[{a_h :> a, x_ :> h[x]}],{{"Kansas", All}, {All, "1802"}}] @ 
 Dataset @ dt

enter image description here

$\endgroup$
4
$\begingroup$
Dataset[dt][{"Kansas" -> 
   KeyValueMap[If[#1 != "1802", #1 -> f[#2], # -> #2] &]/*
    Association}, {"1802" -> f}]

Or

Dataset[dt][{"Kansas"->MapAt[f,{{"1801"},{"1803"},{"1804"}}]},{"1802"->f}]

Or

Dataset[dt][{"Kansas"->(<|f/@#,KeyTake[#,"1802"]|>&)},{"1802"->f}]

enter image description here

$\endgroup$
2
  • $\begingroup$ I think in your 2nd solution (which is very nice) you forgot the {1804} $\endgroup$
    – eldo
    Commented Jul 31, 2023 at 8:04
  • $\begingroup$ @eldo fixed..... $\endgroup$
    – yode
    Commented Jul 31, 2023 at 8:35
2
$\begingroup$

Proceeding as the OP suggests, but using MapAt rather than Map, and dropping the appropriate Key to avoid the double function call.

Query[<|#,"Kansas"->MapAt[f, #"Kansas", Keys[KeyDrop[#"Kansas","1802"], List]]|>&,
      <|#, "1802" -> f @ #"1802"|> &
     ]@dt//Dataset

enter image description here

Calling Normal on the above shows that 'underneath the hood" Query is using RigthtComposition.

Map[<|#, "1802" -> f[#"1802"]|> &] /* (
    <|#, "Kansas" -> MapAt[f, #Kansas, Keys[KeyDrop[#Kansas, "1802"], List]] |> &
  )@dt//Dataset

(* Same Result as above *) 

Original Answer

Generate row and column indices for the third argument of MapAt,deleting the duplicate value:

MapAt[f,Splice@Thread[#]&/@{{2,Range[4]},
    {Range[4],2}}//DeleteDuplicates]@dt//Dataset

Dataset One

Or:

rowIndices[rowName_]:=Thread@{PositionIndex[Keys[#]][rowName[[1]],
  Range@Last@Dimensions[#]}&

colIndices[colName_]:=Thread@{Range@First@Dimensions[#],
  PositionIndex[Keys[First[#]]][colName][[1]]}&

dt//rowIndices["Kansas"]
dt//colIndices["1802"]

(* {{2, 1}, {2, 2}, {2, 3}, {2, 4}} *)
(* {{1, 2}, {2, 2}, {3, 2}, {4, 2}} *)
MapAt[IntegerQ,#,Join[rowIndices["Kansas"][#],
  colIndices["1802"][#]]//DeleteDuplicates]&@Dataset[dt]

Dataset two

$\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.