6
$\begingroup$

I have a function that takes in a list as an argument

Each element in the list is in the form {test number,first name,family name,mark} or {test number,first name,family name} (where the missing mark indicates the student didnt take the test).

I wish to use this function on the list to put it into the form {first name, family name, test1_mark, test2_mark,...} where each list member contains only the test marks for each student. How would i go about doing this? My code so far is:

f3[x_] := Map[{#[[2]], #[[3]], If[Length[#] > 3, #[[4]], "Not taken"]} &, x]

The list is ml = {{1, David, Cameron, 22}, {2, Ed, Milliband, 18}, {3, David, Cameron, 11}, {1, Barak, Obama, 13}, {1, Edward, Windsor}, {2, Edward, Windsor}, {3, Edward, Windsor}, {4, David, Cameron, 18}};

and outputting on the list in the required form gives{{David, Cameron, 22}, {Ed, Milliband, 18}, {David, Cameron, 11}, {Barak, Obama, 13}, {Edward, Windsor, "Not taken"}, {Edward, Windsor, "Not taken"}, {Edward, Windsor, "Not taken"}, {David, Cameron, 18}}

This isnt the desired result as i want it in the form where each element is {first name, family name, test1_mark, test2_mark,...}

$\endgroup$
7
  • $\begingroup$ You say: I have a function that takes in a list as an argument. Please edit the question adding the code that defines that function. We will need to see the code as baseline for making an answer. $\endgroup$ – m_goldberg Feb 4 '17 at 17:45
  • $\begingroup$ You might find it useful to look at Association and Dataset in the Documentation Center. These data structuring objects are often better than lists for handling structured data such as you discuss in your question. $\endgroup$ – m_goldberg Feb 4 '17 at 17:50
  • $\begingroup$ Please also show what format the data has (maybe two or three lines) and what you want the output format to be for that specific input. Also, underscore _ is a meaningful symbol in Mathematica, and so you may not be able to straightforwardly have an output 1_98 (test1, score 98). $\endgroup$ – bill s Feb 4 '17 at 17:51
  • $\begingroup$ I also suggest you consider using some kind of place holder for tests a student did not take. Missing is often used for such. Doing so will make further processing easier. $\endgroup$ – m_goldberg Feb 4 '17 at 17:53
  • $\begingroup$ Please provide data to work with. $\endgroup$ – Kuba Feb 5 '17 at 19:32
1
$\begingroup$

Check this out...

First thing's first, lets establish our working sample:

scores = {
 {1, David, Cameron, 22}, 
 {2, Ed, Milliband, 18}, 
 {3, David, Cameron, 11}, 
 {1, Barak, Obama, 13}, 
 {1, Edward, Windsor}, 
 {2, Edward, Windsor}, 
 {3, Edward, Windsor}, 
 {4, David, Cameron, 18}
}

Next, let's make sure that anybody who has an entry without a grade, has that entry initialized to Null. Also, we'll sort the entries for each student in ascending order wrt to the test number (field no. 1):

gs = Sort /@
  GatherBy[
   scores /. {
     patt_ :> Flatten[{patt, Null}] /; Length[patt] == 3
    }, #[[{2, 3}]] &]

After that, we need to establish which test each student has missed. In order to achieve that we assume that the greatest numbered test available on our score database should be equal to the total number of tests that each of the students should have sat.

pt = Part[gs, All, All, 1];
mx = Max@pt;
rng = Range[mx];
cmpt = Complement[rng, #] & /@ pt

Using Complement[] we can identify the test numbers each student missed (we will need this piece of information in order to complete our score database).

mt = MapThread[(
  templ = #1[[1]];
   Sort[Flatten[{#1, Flatten[{#, Most[Rest[templ]], Null}] & /@ #2}, 1]]
 ) &, {gs, cmpt}]

What does that piece of code up above do? It visits each student's scorecard in turn and adds any test that wasn't part of it. In order to do that, we use as a template the first entry in each student's score card and then we fill it up with all (any) of the missing test scores. Finally, we sort wrt to test number.

Finally, this piece of code puts the results in the desired form:

Apply[
 Flatten[
  Insert[
    Most[Rest[First[{##}]]],
     Part[{##}, All, -1], -1]] &, mt, 1]

(What it does, is to take the records for each student and Insert[] to the name, surname pair all the test scores in the correct order).

hope that helps

$\endgroup$
0
$\begingroup$

This is a start. It does not deal with the issue of the test number. You can use GroupBy

Rewriting test list:

 ml = {{1, "David", "Cameron", 22}, {2, "Ed", "Milliband", 18}, {3, 
    "David", "Cameron", 11}, {1, "Barak", "Obama", 13}, {1, "Edward", 
    "Windsor"}, {2, "Edward", "Windsor"}, {3, "Edward", 
    "Windsor"}, {4, "David", "Cameron", 18}};

So,

g = GroupBy[ml /. {a_, b_, c_} :> {a, b, c,"Not Taken"}, 
{#[[2]], #[[3]]} &, {#1[[1]], #2[[1]],Sequence @@ #3} & @@ Transpose[#[[All, 2 ;;]]] &];

TableForm[Values[g]]

enter image description here

Dealing with test number:

group = GroupBy[
  ml /. {a_, b_, c_} :> {a, b, c, 
     "Not Taken"}, {#[[2]], #[[3]]} &, {#2[[1]], #3[[1]], 
      Association[Thread[#1 -> #4]]} & @@ Transpose[#[[All, 1 ;;]]] &]
v = Values[group];
tr = Values /@ (#[[Ordering[Keys@#]]] & /@ KeyUnion[v[[All, 3]]]);
TableForm[Join @@@ Thread[{v[[All, {1, 2}]], tr}], 
 TableHeadings -> {None, {"First name", "Last name", "Test1", "Test2",
     "Test3", "Test4"}}]

enter image description here

$\endgroup$
0
$\begingroup$

Quick 'n dirty:

ml /. {a_, b_, c_} :> {a, b, c, "Not taken"} /. {a_, b_, c_, d_} :> {b, c, d})

{{David, Cameron, 22}, {Ed, Milliband, 18}, {David, Cameron, 11}, {Barak, Obama, 13}, {Edward, Windsor, "Not taken"}, {Edward, Windsor, "Not taken"}, {Edward, Windsor, "Not taken"}, {David, Cameron, 18}}

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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