Choose Language Hide Translation Bar
Highlighted
rfeick
Level IV

Mentor Mentee Matching

I am trying to write a JMP script to create mentee/mentor matches for a mentoring program at my company. I've got the portion that accounts for mentee and mentor ranking preferences that results in a matrix of preferred mentor for each mentee by rank (1 being the best and 10 being the worst). The issue I'm running into is how to handle it if there are multiple mentees with the same mentor at a given rank (example Mentee 1, Mentee 4, and Mentee 7 all have Mentor 2 at Rank 1). The decisions aren't difficult to do manually, but I can't figure out how to script them. Manual decisions work for the initial pilot with a small group of test participants, but I would like to script something if possible for when the program is expanded. Is this something that's possible in JMP?

 

Please see the attached table for the structure of the matrix matching table. In the table I used M# to designate mentors, E# to designate mentees, and R# to designate ranks.

2 ACCEPTED SOLUTIONS

Accepted Solutions
Highlighted
ih
ih
Level VII

Re: Mentor Mentee Matching

It sounds like something that is very possible, but you probably need to define all of those steps that you indicated are 'easy' for a user to do.  I suggest starting by writing out the method you use to match the mentors and mentees using 'pseudo-code', or comments in your script, and then make sure you can follow that process a few times before starting to write the JSL code for each step.  Here is an example of some (very incomplete) pseudo-code:

 

//Find the highest ranked requests by mentees
  //for each one
    //check if any other mentee request the same person
      //if so see if any mentors requested the same mentee
        //if so...
        //if not....
      //if no mentors requested the same mentee then randomly pick a match
....

One benefit of this technique is once you write the code below each comment, you will leave a good description of what that code does.

View solution in original post

Highlighted
Craige_Hales
Staff (Retired)

Re: Mentor Mentee Matching

@ih describes exactly what I did: do it manually and then try to code it.

Here's a script that needs some more testing. The top section can use your table with character data or generate some test data. Further down, there is a costfun, in two variations (mean, rms), that describes the cost of getting a poor choice of mentor. The table is colored with the final assignments. I'm pretty certain the swaps the JSL does are not enough to find the best possible answer in all cases (which might require three or more rows swapped about, not just a pair.) But it is pretty close.

Mentees are blue, mentors are yellowMentees are blue, mentors are yellow

 

If( 1, // use original file?
	dt = Open( "z:/match lists.jmp" );

	// load the numeric mentee names
	e = J( N Rows( dt ), 1, . );// matrix to hold numeric names
	Parallel Assign( {charnames = dt[0, 1]}, e[r] = Num( Substr( charnames[r], 2 ) ) );

	// load the numeric mentor names
	m = J( N Rows( dt ), N Cols( dt ) - 1, . );// matrix to hold numeric names
	Parallel Assign( {charnames = dt[0, 2 :: N Cols( dt )]}, m[r, c] = Num( Substr( charnames[r][c], 2 ) ) );
, // else make up some test data
	n = 50;// N^2...1000 takes 2 minutes
	m = J( n, n, . );
	For( i = 1, i <= N Rows( m ), i += 1,
		m[i, 0] = Random Shuffle( 1 :: N Rows( m ) )
	);
	e = (1 :: N Rows( m ))`;
	dt = As Table( e || m );
);
	
// check:
If( !Min( e ) == 1 | !Min( m ) == 1 | !Max( e ) == N Rows( e ) | !Max( m ) == N Rows( e ),
	Throw( "the E and M suffixes are not 1..nrows" )
);

// make an initial assignment of mentors to mentees

cost = J( N Rows( e ), 1, . );// 1..nMentors, index into this mentee's ranked list of mentors
used = J( N Rows( e ), 1, . );// for initial assignment, has this mentor been used?
For( menteeIndex = 1, menteeIndex <= N Rows( e ), menteeIndex += 1,
	For( candidateIndex = 1, candidateIndex <= N Cols( m ), candidateIndex += 1,
		candidate = m[menteeIndex, candidateIndex];
		If( Is Missing( used[candidate] ), //
			used[candidate] = 1;
			cost[menteeIndex] = candidateIndex;
			candidateIndex = 9e99;// terminate the loop AND indicate found
		);
	);
	If( candidateIndex < 9e99,
		Throw( "no candidate found for row " || Char( menteeIndex ) )
	);
);
If( !All( used == 1 ),
	Throw( "some mentors were not used" )
);// this might be acceptable, test carefully


// rms error cost function means first+third choices is worse than second+second choices
// ... you might not agree and might want a different cost function
//cost=[2,2]; // -> 2
//cost=[1,3]; // -> 2.236
// in particular, you might not like...
//cost=[1,10]; // -> 7.1
//cost=[4,9]; // -> 6.96
// maybe you want a simpler mean of the individual costs?




costfun = Function( {c}, Sqrt( Mean( c :* c ) ) );

//costfun = Function( {c}, Mean( c ) );




// try doing some swaps to improve the costs
// this gets really close to a best answer but may not
// be perfect if a 3-swap is needed; also there may
// be two equal scores (esp with simple mean) that have
// different worst case assignments (1,3 vs 2,2 for example).
gotBetter = 1;
While( gotBetter,
	gotBetter = 0;
	Show( costfun( cost ) );
	For( myMenteeIndex = 1, myMenteeIndex <= N Rows( e ), myMenteeIndex += 1,
		myCandidateIndex = cost[myMenteeIndex];
		If( myCandidateIndex > 1,
			myCandidate = m[myMenteeIndex, myCandidateIndex];
			oldcost = costfun( cost );
			// look for a swap
			For( hisMenteeIndex = 1, hisMenteeIndex <= N Rows( e ), hisMenteeIndex += 1,
				If( myMenteeIndex != hisMenteeIndex,
					hisCandidateIndex = cost[hisMenteeIndex];
					hisCandidate = m[hisMenteeIndex, hisCandidateIndex];
					hisSwap = Loc( m[hisMenteeIndex, 0] == myCandidate );
					mySwap = Loc( m[myMenteeIndex, 0] == hisCandidate );
					// trial:
					cost[myMenteeIndex] = mySwap;
					cost[hisMenteeIndex] = hisSwap;
					newcost = costfun( cost );
					If( newcost < oldcost,
						gotBetter = 1;// another round might get even better
						//show(costfun(cost));wait(0);
						Break();// resets myCandidate
					, // revert
						cost[myMenteeIndex] = myCandidateIndex;
						cost[hisMenteeIndex] = hisCandidateIndex;
						//
					);
				)
			);
		);
	);
);

//worstMentee = 1;
Column( dt, 1 ) << colorcells( "cyan" );
For( menteeIndex = 1, menteeIndex <= N Rows( e ), menteeIndex += 1,
	Column( dt, cost[menteeIndex] + 1 ) << colorcells( "yellow", menteeIndex );
//	If( cost[worstMentee] < cost[menteeIndex],
//		worstMentee = menteeIndex
//	);// well, one of the worst, could be ties
);
//worstMentor = m[worstMentee, cost[worstMentee]];
//For( mentorIndex = 1, mentorIndex <= N Cols( m ), mentorIndex += 1,
//	col = Column( dt, mentorIndex + 1 );
//	For( menteeIndex = 1, menteeIndex <= N Rows( e ), menteeIndex += 1,
//		If( menteeIndex != worstMentee,
//			If( col[menteeIndex] == worstMentor,
//				col << colorcells( "lightred", menteeIndex )
//			)
//		)
//	);
//);

 That JSL has some bad N^2 behavior: for every row it studies every row...around 1000 mentor/mentee pairs it is really starting to slow down...just when you really need it to work. 

 

Craige

View solution in original post

4 REPLIES 4
Highlighted
ih
ih
Level VII

Re: Mentor Mentee Matching

It sounds like something that is very possible, but you probably need to define all of those steps that you indicated are 'easy' for a user to do.  I suggest starting by writing out the method you use to match the mentors and mentees using 'pseudo-code', or comments in your script, and then make sure you can follow that process a few times before starting to write the JSL code for each step.  Here is an example of some (very incomplete) pseudo-code:

 

//Find the highest ranked requests by mentees
  //for each one
    //check if any other mentee request the same person
      //if so see if any mentors requested the same mentee
        //if so...
        //if not....
      //if no mentors requested the same mentee then randomly pick a match
....

One benefit of this technique is once you write the code below each comment, you will leave a good description of what that code does.

View solution in original post

Highlighted
Craige_Hales
Staff (Retired)

Re: Mentor Mentee Matching

@ih describes exactly what I did: do it manually and then try to code it.

Here's a script that needs some more testing. The top section can use your table with character data or generate some test data. Further down, there is a costfun, in two variations (mean, rms), that describes the cost of getting a poor choice of mentor. The table is colored with the final assignments. I'm pretty certain the swaps the JSL does are not enough to find the best possible answer in all cases (which might require three or more rows swapped about, not just a pair.) But it is pretty close.

Mentees are blue, mentors are yellowMentees are blue, mentors are yellow

 

If( 1, // use original file?
	dt = Open( "z:/match lists.jmp" );

	// load the numeric mentee names
	e = J( N Rows( dt ), 1, . );// matrix to hold numeric names
	Parallel Assign( {charnames = dt[0, 1]}, e[r] = Num( Substr( charnames[r], 2 ) ) );

	// load the numeric mentor names
	m = J( N Rows( dt ), N Cols( dt ) - 1, . );// matrix to hold numeric names
	Parallel Assign( {charnames = dt[0, 2 :: N Cols( dt )]}, m[r, c] = Num( Substr( charnames[r][c], 2 ) ) );
, // else make up some test data
	n = 50;// N^2...1000 takes 2 minutes
	m = J( n, n, . );
	For( i = 1, i <= N Rows( m ), i += 1,
		m[i, 0] = Random Shuffle( 1 :: N Rows( m ) )
	);
	e = (1 :: N Rows( m ))`;
	dt = As Table( e || m );
);
	
// check:
If( !Min( e ) == 1 | !Min( m ) == 1 | !Max( e ) == N Rows( e ) | !Max( m ) == N Rows( e ),
	Throw( "the E and M suffixes are not 1..nrows" )
);

// make an initial assignment of mentors to mentees

cost = J( N Rows( e ), 1, . );// 1..nMentors, index into this mentee's ranked list of mentors
used = J( N Rows( e ), 1, . );// for initial assignment, has this mentor been used?
For( menteeIndex = 1, menteeIndex <= N Rows( e ), menteeIndex += 1,
	For( candidateIndex = 1, candidateIndex <= N Cols( m ), candidateIndex += 1,
		candidate = m[menteeIndex, candidateIndex];
		If( Is Missing( used[candidate] ), //
			used[candidate] = 1;
			cost[menteeIndex] = candidateIndex;
			candidateIndex = 9e99;// terminate the loop AND indicate found
		);
	);
	If( candidateIndex < 9e99,
		Throw( "no candidate found for row " || Char( menteeIndex ) )
	);
);
If( !All( used == 1 ),
	Throw( "some mentors were not used" )
);// this might be acceptable, test carefully


// rms error cost function means first+third choices is worse than second+second choices
// ... you might not agree and might want a different cost function
//cost=[2,2]; // -> 2
//cost=[1,3]; // -> 2.236
// in particular, you might not like...
//cost=[1,10]; // -> 7.1
//cost=[4,9]; // -> 6.96
// maybe you want a simpler mean of the individual costs?




costfun = Function( {c}, Sqrt( Mean( c :* c ) ) );

//costfun = Function( {c}, Mean( c ) );




// try doing some swaps to improve the costs
// this gets really close to a best answer but may not
// be perfect if a 3-swap is needed; also there may
// be two equal scores (esp with simple mean) that have
// different worst case assignments (1,3 vs 2,2 for example).
gotBetter = 1;
While( gotBetter,
	gotBetter = 0;
	Show( costfun( cost ) );
	For( myMenteeIndex = 1, myMenteeIndex <= N Rows( e ), myMenteeIndex += 1,
		myCandidateIndex = cost[myMenteeIndex];
		If( myCandidateIndex > 1,
			myCandidate = m[myMenteeIndex, myCandidateIndex];
			oldcost = costfun( cost );
			// look for a swap
			For( hisMenteeIndex = 1, hisMenteeIndex <= N Rows( e ), hisMenteeIndex += 1,
				If( myMenteeIndex != hisMenteeIndex,
					hisCandidateIndex = cost[hisMenteeIndex];
					hisCandidate = m[hisMenteeIndex, hisCandidateIndex];
					hisSwap = Loc( m[hisMenteeIndex, 0] == myCandidate );
					mySwap = Loc( m[myMenteeIndex, 0] == hisCandidate );
					// trial:
					cost[myMenteeIndex] = mySwap;
					cost[hisMenteeIndex] = hisSwap;
					newcost = costfun( cost );
					If( newcost < oldcost,
						gotBetter = 1;// another round might get even better
						//show(costfun(cost));wait(0);
						Break();// resets myCandidate
					, // revert
						cost[myMenteeIndex] = myCandidateIndex;
						cost[hisMenteeIndex] = hisCandidateIndex;
						//
					);
				)
			);
		);
	);
);

//worstMentee = 1;
Column( dt, 1 ) << colorcells( "cyan" );
For( menteeIndex = 1, menteeIndex <= N Rows( e ), menteeIndex += 1,
	Column( dt, cost[menteeIndex] + 1 ) << colorcells( "yellow", menteeIndex );
//	If( cost[worstMentee] < cost[menteeIndex],
//		worstMentee = menteeIndex
//	);// well, one of the worst, could be ties
);
//worstMentor = m[worstMentee, cost[worstMentee]];
//For( mentorIndex = 1, mentorIndex <= N Cols( m ), mentorIndex += 1,
//	col = Column( dt, mentorIndex + 1 );
//	For( menteeIndex = 1, menteeIndex <= N Rows( e ), menteeIndex += 1,
//		If( menteeIndex != worstMentee,
//			If( col[menteeIndex] == worstMentor,
//				col << colorcells( "lightred", menteeIndex )
//			)
//		)
//	);
//);

 That JSL has some bad N^2 behavior: for every row it studies every row...around 1000 mentor/mentee pairs it is really starting to slow down...just when you really need it to work. 

 

Craige

View solution in original post

Highlighted
rfeick
Level IV

Re: Mentor Mentee Matching

Thanks for all the effort in this! I see a lot of things I didn't know how to do with JSL so I'll learn a lot.
Highlighted
rfeick
Level IV

Re: Mentor Mentee Matching

Thanks for the tip! I had tried psuedocoding it before, but this made me go back and rethink the process and I found a solution to the most problematic spot.

Article Labels