How to apply array:for-each on an array of arrays?

Monday, 30 March 2020 · 10 messages · last reply 31 Mar 2020
BE
Ben Engbers30 Mar 2020 23:16 #

Hi,

In textmining, the 'idf' or inverse document frequency is defined as idf(term)=ln(ndocuments / ndocuments containing term). I am working on a function that should return this idf.

This function:

declare function local:wordFreq_idf($nodes as node()*) as array(*) {
  let $count := count($nodes)
  let $text := for $node in $nodes
     return $node/text() => tokenize() => distinct-values()
     let $idf := $text   => tidyTM:wordCount_arr()
  return $idf
};

returns:

["probleem", 703]
["opgelost.", 248]
["dictu", 235]
["opgelost", 217]
["medewerker", 193]
...

For "probleem", the idf should be calculated as ln($count/703). Since
there are 1780 nodes this would result in 0.929011751.
I tried to exten the 'let $idf' line with:
       => array:for-each(function($idf) {array:append($idf,
math:log($count div $idf[2]) )})
which should result in ["probleem", 703, 0.929011751]

but no mather what I do, every time I get this error: [XPTY0004] Cannot promote (array(xs:anyAtomicType))+ to array(*): ([ "probleem", 703 ], [ "opgelost.", 248 ], ...).

Is it possible to apply array:for-each on an array of arrays?

Ben

BD
Bridger Dyson-Smith31 Mar 2020 01:58 #

Hi Ben - I'm on mobile, please excuse any typos.

Maybe return array { $idf } is closer?

Untested, apologies! Best, Bridger

Quoted text · Ben Engbers · 42 lines
On Mon, Mar 30, 2020, 5:16 PM Ben Engbers <Ben.Engbers at be-logical.nl> wrote: > Hi, > > In textmining, the 'idf' or inverse document frequency is defined as > idf(term)=ln(ndocuments / ndocuments containing term). I am working on a > function that should return this idf. > > This function: > > declare function local:wordFreq_idf($nodes as node()*) as array(*) { > let $count := count($nodes) > let $text := for $node in $nodes > return $node/text() => tokenize() => distinct-values() > let $idf := $text => tidyTM:wordCount_arr() > return $idf > }; > > returns: > > ["probleem", 703] > ["opgelost.", 248] > ["dictu", 235] > ["opgelost", 217] > ["medewerker", 193] > ... > > For "probleem", the idf should be calculated as ln($count/703). Since > there are 1780 nodes this would result in 0.929011751. > I tried to exten the 'let $idf' line with: > => array:for-each(function($idf) {array:append($idf, > math:log($count div $idf[2]) )}) > which should result in ["probleem", 703, 0.929011751] > > but no mather what I do, every time I get this error: > [XPTY0004] Cannot promote (array(xs:anyAtomicType))+ to array(*): ([ > "probleem", 703 ], [ "opgelost.", 248 ], ...). > > Is it possible to apply array:for-each on an array of arrays? > > Ben > >
G
Graydonin reply to Ben Engbers, 30 Mar 2020, 23:1631 Mar 2020 03:19 #
Quoted text · Ben Engbers · 1 line
On Mon, Mar 30, 2020 at 11:16:23PM +0200, Ben Engbers scripsit:

[snip]

Quoted text · Ben Engbers · 10 lines
> For "probleem", the idf should be calculated as ln($count/703). Since > there are 1780 nodes this would result in 0.929011751. > I tried to exten the 'let $idf' line with: > => array:for-each(function($idf) {array:append($idf, > math:log($count div $idf[2]) )}) > which should result in ["probleem", 703, 0.929011751] > > but no mather what I do, every time I get this error: > [XPTY0004] Cannot promote (array(xs:anyAtomicType))+ to array(*): ([ > "probleem", 703 ], [ "opgelost.", 248 ], ...).

The errors says you're trying to feed a sequence of arrays to an array function; maybe you want ! where you have => ?

-- Graydon

BE
Ben Engbers31 Mar 2020 16:21 #
Quoted text · Graydon · 19 lines
Op 31-03-2020 om 01:18 schreef Graydon: > On Mon, Mar 30, 2020 at 11:16:23PM +0200, Ben Engbers scripsit: > [snip] >> For "probleem", the idf should be calculated as ln($count/703). Since >> there are 1780 nodes this would result in 0.929011751. >> I tried to exten the 'let $idf' line with: >> => array:for-each(function($idf) {array:append($idf, >> math:log($count div $idf[2]) )}) >> which should result in ["probleem", 703, 0.929011751] >> >> but no mather what I do, every time I get this error: >> [XPTY0004] Cannot promote (array(xs:anyAtomicType))+ to array(*): ([ >> "probleem", 703 ], [ "opgelost.", 248 ], ...). > > The errors says you're trying to feed a sequence of arrays to an array > function; maybe you want ! where you have => ? > > -- Graydon >

Hi, Upon your remark about feeding a sequence of arrays, I first tried to apply 'for-each' instead of 'array:for-each'. Alas, that didn't help ;-(, the error was still the same. I then tried to understand what you mean with the '!'. In the book from Priscilla Walmsley, the ! is mentioned as a simple map operator. How is that related to this problem?

Cheers, Ben

MH
Martin Honnenin reply to Ben Engbers, 30 Mar 2020, 23:1631 Mar 2020 16:29 #
Quoted text · Ben Engbers · 25 lines
Am 30.03.2020 um 23:16 schrieb Ben Engbers: > Hi, > > In textmining, the 'idf' or inverse document frequency is defined as > idf(term)=ln(ndocuments / ndocuments containing term). I am working on a > function that should return this idf. > > This function: > > declare function local:wordFreq_idf($nodes as node()*) as array(*) { > let $count := count($nodes) > let $text := for $node in $nodes > return $node/text() => tokenize() => distinct-values() > let $idf := $text => tidyTM:wordCount_arr() > return $idf > }; > > returns: > > ["probleem", 703] > ["opgelost.", 248] > ["dictu", 235] > ["opgelost", 217] > ["medewerker", 193] > ...

So does the working function return a sequence of arrays? That doesn't match the as array(*) return type declaration, it seems.

What does tidyTM:wordCount_arr() return, a single array (of atomic items)?

BE
Ben Engbersin reply to Graydon, 31 Mar 2020, 18:5631 Mar 2020 18:43 #

Hi,

Quoted text · Graydon · 2 lines
> => means "take the thing on the left and substitute it for the first > parameter of the function on the right, so

I thought it meant "The first parameter on the right will be subsituted with the thing on the left"?

Quoted text · Graydon · 8 lines
> ('weasels') => replace('weasels','mustelids') works > > ('weasels','badgers') => replace('weasels','mustelids') DOES NOT work > > This is because a one-item sequence can be treated as the single string > value the first parameter of replace() requires, but a > greater-then-one-item sequence can't be. (This one gives you "item > expected, sequence found" if you try it from the GUI.)

The following is quite similar to the 'piping' mechanism in R. I'll start experimenting with it.

Thanx, Ben

Quoted text · Graydon · 21 lines
> ! means "take each item of the sequence on the left and pass it to the > thing on the right in turn", so > > ('weasels','badgers') ! replace(.,'weasels','mustelids') works. > > (note that replace() got its first parameter back as the context item > dot.) > > so if you take > > => array:for-each(function($idf) {array:append($idf,math:log($count div $idf[2]) )}) > > and replace it with > ! array:for-each(.,function($idf) {array:append($idf,math:log($count div $idf[2]) )}) > > (note the context-item dot!) > > you should at least get a different error message. > > -- Graydon >
G
Graydonin reply to Ben Engbers, 31 Mar 2020, 16:2131 Mar 2020 18:56 #
Quoted text · Ben Engbers · 21 lines
On Tue, Mar 31, 2020 at 04:21:52PM +0200, Ben Engbers scripsit: > Op 31-03-2020 om 01:18 schreef Graydon: > > On Mon, Mar 30, 2020 at 11:16:23PM +0200, Ben Engbers scripsit: > > [snip] > >> For "probleem", the idf should be calculated as ln($count/703). Since > >> there are 1780 nodes this would result in 0.929011751. > >> I tried to exten the 'let $idf' line with: > >> => array:for-each(function($idf) {array:append($idf, > >> math:log($count div $idf[2]) )}) > >> which should result in ["probleem", 703, 0.929011751] > >> > >> but no mather what I do, every time I get this error: > >> [XPTY0004] Cannot promote (array(xs:anyAtomicType))+ to array(*): ([ > >> "probleem", 703 ], [ "opgelost.", 248 ], ...). > > > > The errors says you're trying to feed a sequence of arrays to an array > > function; maybe you want ! where you have => ? > > Upon your remark about feeding a sequence of arrays, I first tried to > apply 'for-each' instead of 'array:for-each'. Alas, that didn't help > ;-(, the error was still the same.

array:for-each takes a single array and gives you back a new array based on what the anonymous function passed as the second parameter does to each member of the original array.

So you have to make sure you're feeding a single array to it. (and you're not; that's what the error message is telling you, you've got a sequence of arrays on the left of the => operator.)

Quoted text · Ben Engbers · 3 lines
> I then tried to understand what you mean with the '!'. > In the book from Priscilla Walmsley, the ! is mentioned as a simple map > operator. How is that related to this problem?

=> means "take the thing on the left and substitute it for the first parameter of the function on the right, so

('weasels') => replace('weasels','mustelids') works

('weasels','badgers') => replace('weasels','mustelids') DOES NOT work

This is because a one-item sequence can be treated as the single string value the first parameter of replace() requires, but a greater-then-one-item sequence can't be. (This one gives you "item expected, sequence found" if you try it from the GUI.)

! means "take each item of the sequence on the left and pass it to the thing on the right in turn", so

('weasels','badgers') ! replace(.,'weasels','mustelids') works.

(note that replace() got its first parameter back as the context item dot.)

so if you take

=> array:for-each(function($idf) {array:append($idf,math:log($count div $idf[2]) )})

and replace it with ! array:for-each(.,function($idf) {array:append($idf,math:log($count div $idf[2]) )})

(note the context-item dot!)

you should at least get a different error message.

-- Graydon

BE
Ben Engbersin reply to Martin Honnen, 31 Mar 2020, 16:2931 Mar 2020 20:35 #

Hi,

For (my personal) clarity, I have split up the original function in two parts:

declare function local:step_one($nodes as node()*) as array(*)*
{
  let $text := for $node in $nodes
     return $node/text() =>
     tokenize() => distinct-values()
  let $idf := $text   =>
     tidyTM:wordCount_arr()
  return $idf
};

In local:step_one(), I first create a sequence with the distinct tokens
for each $node. All the sequences are joined in $text.
I then call wordCount_arr to count the occurences of each word in $text:

declare function tidyTM:wordCount_arr(
  $Words as xs:string*)
  as array(*) {
for $w in $Words
  let $f := $w
  group by $f
  order by count($w) descending
return ([$f, count($w)])
} ;

I would say that tidyTM:wordCount_arr returns a sequence of arrays but I am not certain if I have specified the correct return-type?

Calling local:step_one(tidyTM:remove_Stopwords($nodes, "Stp", $Stoppers))
returns:
["probleem", 703]
["opgelost.", 248]
....

I had hoped that calling the following local:wordFreq, would add the idf to each element but instead I get an error

declare function local:wordFreq_idf($nodes as node()*)  as array(*)
{
  let $count := count($nodes)
  let $idf := local:step_one($nodes)
  let $result := for-each( $idf,
    function($z) {array:append ($z, math:log($count div $z(2) ) ) } )
  return $result
};
[XPTY0004] Cannot promote (array(xs:anyAtomicType))+ to array(*): $idf
:= ([ "probleem", 703 ], [ "opgelost.", 248 ], ...).

Cheers, Ben

Quoted text · Martin Honnen · 7 lines
Op 31-03-2020 om 16:29 schreef Martin Honnen: > So does the working function return a sequence of arrays? That doesn't > match the > as array(*) > return type declaration, it seems. > > What does tidyTM:wordCount_arr() return, a single array (of atomic items)?
MH
Martin Honnen31 Mar 2020 22:33 #
Quoted text · Ben Engbers · 32 lines
On 31.03.2020 18:32, Ben Engbers wrote: > Hi, > > For (my personal) clarity, I have split up the original function in two > parts: > > declare function local:step_one($nodes as node()*) as array(*)* > { > let $text := for $node in $nodes > return $node/text() => > tokenize() => distinct-values() > let $idf := $text => > tidyTM:wordCount_arr() > return $idf > }; > > In local:step_one(), I first create a sequence with the distinct tokens > for each $node. All the sequences are joined in $text. > I then call wordCount_arr to count the occurences of each word in $text: > > declare function tidyTM:wordCount_arr( > $Words as xs:string*) > as array(*) { > for $w in $Words > let $f := $w > group by $f > order by count($w) descending > return ([$f, count($w)]) > } ; > > I would say that tidyTM:wordCount_arr returns a sequence of arrays but I > am not certain if I have specified the correct return-type?

Reading the code I agree that the return type seems to be a sequence of arrays but therefore I wonder why you don't get a similar error as later on with declaring array(*) and not array(*)*

Quoted text · Ben Engbers · 19 lines
> Calling local:step_one(tidyTM:remove_Stopwords($nodes, "Stp", $Stoppers)) > returns: > ["probleem", 703] > ["opgelost.", 248] > .... > > I had hoped that calling the following local:wordFreq, would add the > idf to each element but instead I get an error > > declare function local:wordFreq_idf($nodes as node()*) as array(*) > { > let $count := count($nodes) > let $idf := local:step_one($nodes) > let $result := for-each( $idf, > function($z) {array:append ($z, math:log($count div $z(2) ) ) } ) > return $result > }; > [XPTY0004] Cannot promote (array(xs:anyAtomicType))+ to array(*): $idf > := ([ "probleem", 703 ], [ "opgelost.", 248 ], ...).

The message tries to tell you that the declared return type array(*) is a single array while the function returns a (non-empty) sequence of arrays so using declare function local:wordFreq_idf($nodes as node()*) as array(*)* would remove that error.

To insert the third value into each array I think you want

   let $result := $idf ! array:append(., math:log($count div .(2) ))
BE
Ben Engbers31 Mar 2020 23:17 #

Hi,

Quoted text · Martin Honnen · 3 lines
> To insert the third value into each array I think you want > > let $result := $idf ! array:append(., math:log($count div .(2) ))

This works!

Martin and Graydon, thanks for the help and the explanation.

Ben

import module namespace tidyTM = 'http://www.be-logical.nl';

declare function local:step_one($nodes as node()*) as array(*)*
{
  let $text := for $node in $nodes
     return $node/text() =>
     tokenize() => distinct-values()
  let $idf := $text   =>
     tidyTM:wordCount_arr()
  return $idf
};

declare function local:wordFreq_idf($nodes as node()*)  as array(*)
{
  let $count := count($nodes)
  let $idf := local:step_one($nodes)
  let $result := $idf ! array:append(., math:log($count div .(2) ))
  return $result
};

let $nodes :=
collection('IncidentRemarks/Incidenten-180101-190630.csv')/csv/record/INC_RM
let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text()

return local:wordFreq_idf(
  tidyTM:remove_Stopwords($nodes, "Stp", $Stoppers))

--------------

declare function tidyTM:wordCount_arr(
  $Words as xs:string*)
  as array(*)* {
for $w in $Words
  let $f := $w
  group by $f
  order by count($w) descending
return ([$f, count($w)])
} ;

-----------

["probleem", 703, 9.362885817944681e-1]
["opgelost.", 248, 1.9782167274401508e0]
...

⚡Generated with XQuery